SOS14 on a Xenon button press

Have an odd issue that impacts one of my Xenon's but not others. I setup a button handler to output the duration of the button press and the String(duration) is causing an SOS14. According to the docs:

Semaphore lock timeout ( Since 0.8.0 60 seconds expired while trying to acquire a semaphore lock, likely due to dynamic memory allocation)

But the error is instant, not 60sec later. With TRACE level logging there is nothing shown between button push and crash.

This has been happening for a few months on different firmware versions but today I'm running it on 1.2.1-rc.3, here's my test code

SerialLogHandler logHandler(LOG_LEVEL_TRACE, { { "app", LOG_LEVEL_TRACE  } });

void setup() {
  Serial.begin();
  Serial.println("app start");
  System.on(button_status, button_handler); //detect mode button interaction
}

void loop() {
}

void button_handler(system_event_t event, int duration, void* )
{
    //works
    Serial.print("button pushed, duration =");
    Serial.println(duration);
    
    //Causes SOS 14: Semaphore lock timeout (Since 0.8.0 60 seconds expired while trying to acquire a semaphore lock, likely due to dynamic memory allocation)
    Serial.println("Mode button had been pushed for " + String(duration));
}

The odd thing is with that last Serial uncommented, nothing comes through the serial, even with a decent delay. But comment it out and the 1st 2 Serial prints work. Likewise if you change that 2nd Serial to use String it'll crash there; ie
Serial.println(String(duration));

By the way, using String([some int]) works within loop so it's only in the handler that it causes this error.

I know there are better ways to do this but this seems like a bug.

The button handler runs at an interrupt context. Because of this, you cannot do anything that allocates memory. The String object does, which is what causes the SOS 14.

Actually, using Serial is also not recommended, because Serial is not automatically protected against simultaneous access from threads or interrupts. It’s best to just set a volatile flag from the button handler and handle the action from loop.

1 Like

Interesting, any idea why it works on my other Xenons?
Also I’ve found Log statements don’t work or are unreliable in handlers, which is why I went with serial for this test case.

Log.info, Log.error, etc. don’t log when called from an interrupt context, unfortunately.

Older versions of Device OS don’t guard against allocating memory from an interrupt context. While it often works, it also periodically corrupts the heap, causing the program to crash sometime later, usually for something completely unrelated, which is impossible to debug.

This probably works:

Serial.printlnf("Mode button had been pushed for %d", duration);

Though %f (float) cannot be used safely from an interrupt context. As the rules are confusing and poorly documented, I’d stick with doing as little as possible from the button handler.

Got it, thanks for clearing that up for me :smile:

Is this true for snprintf() too?

Is this true for snprintf() too?

Yes, the memory allocation is in the gcc-arm C standard library snprintf, so it affects snprintf, Serial.printlnf, Log.info, etc..