Skip to content

Commit f961384

Browse files
committed
handle executing code that uses JS vars in an IRQ
1 parent 237c3ed commit f961384

File tree

4 files changed

+46
-13
lines changed

4 files changed

+46
-13
lines changed

src/jsutils.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -371,13 +371,15 @@ void jsWarn_flash(const char *fmt, ...);
371371
#endif
372372

373373
// ------------
374+
/// Error flags for internal errors - update jswrap_espruino_getErrorFlags if you add to this
374375
typedef enum {
375376
JSERR_NONE = 0,
376377
JSERR_RX_FIFO_FULL = 1, ///< The IO buffer (ioBuffer in jsdevices.c) is full and data was lost. Happens for character data and watch events
377378
JSERR_BUFFER_FULL = 2, ///< eg. Serial1's buffer exceeded the max size. Doesn't happen if you have an on('data') callback
378379
JSERR_CALLBACK = 4, ///< A callback (on data/watch/timer) caused an error and was removed
379380
JSERR_LOW_MEMORY = 8, ///< Memory is running low - Espruino had to run a garbage collection pass or remove some of the command history
380381
JSERR_MEMORY = 16, ///< Espruino ran out of memory and was unable to allocate some data that it needed.
382+
JSERR_MEMORY_BUSY = 32, ///< Espruino was busy doing something with memory (eg. garbage collection) so an IRQ couldn't allocate memory
381383
} PACKED_FLAGS JsErrorFlags;
382384

383385
/** Error flags for things that we don't really want to report on the console,

src/jsvar.c

Lines changed: 42 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,8 @@ JsVar jsVars[JSVAR_CACHE_SIZE];
4545
unsigned int jsVarsSize = JSVAR_CACHE_SIZE;
4646
#endif
4747

48-
JsVarRef jsVarFirstEmpty; ///< reference of first unused variable (variables are in a linked list)
48+
volatile JsVarRef jsVarFirstEmpty; ///< reference of first unused variable (variables are in a linked list)
49+
volatile bool isMemoryBusy; ///< Are we doing garbage collection or similar, so can't access memory?
4950

5051
// ----------------------------------------------------------------------------
5152
// ----------------------------------------------------------------------------
@@ -161,6 +162,8 @@ void jsvSetMaxVarsUsed(unsigned int size) {
161162

162163
// maps the empty variables in...
163164
void jsvCreateEmptyVarList() {
165+
assert(!isMemoryBusy);
166+
isMemoryBusy = true;
164167
jsVarFirstEmpty = 0;
165168
JsVar firstVar; // temporary var to simplify code in the loop below
166169
jsvSetNextSibling(&firstVar, 0);
@@ -179,12 +182,15 @@ void jsvCreateEmptyVarList() {
179182
}
180183
jsvSetNextSibling(lastEmpty, 0);
181184
jsVarFirstEmpty = jsvGetNextSibling(&firstVar);
185+
isMemoryBusy = false;
182186
}
183187

184188
/* Removes the empty variable counter, cleaving clear runs of 0s
185189
where no data resides. This helps if compressing the variables
186190
for storage. */
187191
void jsvClearEmptyVarList() {
192+
assert(!isMemoryBusy);
193+
isMemoryBusy = true;
188194
jsVarFirstEmpty = 0;
189195
JsVarRef i;
190196
for (i=1;i<=jsVarsSize;i++) {
@@ -197,6 +203,7 @@ void jsvClearEmptyVarList() {
197203
i = (JsVarRef)(i+jsvGetFlatStringBlocks(var));
198204
}
199205
}
206+
isMemoryBusy = false;
200207
}
201208

202209
void jsvSoftInit() {
@@ -281,6 +288,8 @@ unsigned int jsvGetMemoryTotal() {
281288
/// Try and allocate more memory - only works if RESIZABLE_JSVARS is defined
282289
void jsvSetMemoryTotal(unsigned int jsNewVarCount) {
283290
#ifdef RESIZABLE_JSVARS
291+
assert(!isMemoryBusy);
292+
isMemoryBusy = true;
284293
if (jsNewVarCount <= jsVarsSize) return; // never allow us to have less!
285294
// When resizing, we just allocate a bunch more
286295
unsigned int oldSize = jsVarsSize;
@@ -298,6 +307,7 @@ void jsvSetMemoryTotal(unsigned int jsNewVarCount) {
298307
assert(!jsVarFirstEmpty);
299308
jsVarFirstEmpty = jsvInitJsVars(oldSize+1, jsVarsSize-oldSize);
300309
// jsiConsolePrintf("Resized memory from %d blocks to %d\n", oldBlockCount, newBlockCount);
310+
isMemoryBusy = false;
301311
#else
302312
NOT_USED(jsNewVarCount);
303313
assert(0);
@@ -367,24 +377,40 @@ void jsvResetVariable(JsVar *v, JsVarFlags flags) {
367377
}
368378

369379
JsVar *jsvNewWithFlags(JsVarFlags flags) {
380+
if (isMemoryBusy) {
381+
jsErrorFlags |= JSERR_MEMORY_BUSY;
382+
return 0;
383+
}
370384
if (jsVarFirstEmpty!=0) {
371-
assert(jsvGetAddressOf(jsVarFirstEmpty)->flags == JSV_UNUSED);
372385
jshInterruptOff(); // to allow this to be used from an IRQ
373386
JsVar *v = jsvGetAddressOf(jsVarFirstEmpty); // jsvResetVariable will lock
374-
jsVarFirstEmpty = jsvGetNextSibling(v); // move our reference to the next in the free list
387+
jsVarFirstEmpty = jsvGetNextSibling(v); // move our reference to the next in the fr
375388
jshInterruptOn();
389+
assert(v->flags == JSV_UNUSED);
390+
// Cope with IRQs/multi-threading when getting a new free variable
391+
/* JsVarRef empty;
392+
JsVarRef next;
393+
JsVar *v;
394+
do {
395+
empty = jsVarFirstEmpty;
396+
v = jsvGetAddressOf(empty); // jsvResetVariable will lock
397+
next = jsvGetNextSibling(v); // move our reference to the next in the free list
398+
} while (!__sync_bool_compare_and_swap(&jsVarFirstEmpty, empty, next));
399+
assert(v->flags == JSV_UNUSED);*/
376400
jsvResetVariable(v, flags); // setup variable, and add one lock
377401
// return pointer
378402
return v;
379403
}
380404
jsErrorFlags |= JSERR_LOW_MEMORY;
381405
/* we don't have memory - second last hope - run garbage collector */
382-
if (jsvGarbageCollect())
406+
if (jsvGarbageCollect()) {
383407
return jsvNewWithFlags(flags); // if it freed something, continue
408+
}
384409
/* we don't have memory - last hope - ask jsInteractive to try and free some it
385410
may have kicking around */
386-
if (jsiFreeMoreMemory())
411+
if (jsiFreeMoreMemory()) {
387412
return jsvNewWithFlags(flags);
413+
}
388414
/* We couldn't claim any more memory by Garbage collecting... */
389415
#ifdef RESIZABLE_JSVARS
390416
jsvSetMemoryTotal(jsVarsSize*2);
@@ -628,6 +654,11 @@ JsVarRef jsvUnRefRef(JsVarRef ref) {
628654
}
629655

630656
JsVar *jsvNewFlatStringOfLength(unsigned int byteLength) {
657+
if (isMemoryBusy) {
658+
jsErrorFlags |= JSERR_MEMORY_BUSY;
659+
return 0;
660+
}
661+
isMemoryBusy = true;
631662
// Work out how many blocks we need. One for the header, plus some for the characters
632663
size_t blocks = 1 + ((byteLength+sizeof(JsVar)-1) / sizeof(JsVar));
633664
// Now try and find them
@@ -695,18 +726,15 @@ JsVar *jsvNewFlatStringOfLength(unsigned int byteLength) {
695726
}
696727
jsvSetNextSibling(lastEmpty, 0);
697728
jsVarFirstEmpty = jsvGetNextSibling(&firstVar);
698-
729+
isMemoryBusy = false;
699730
// Return whatever we had (0 if we couldn't manage it)
700731
return flatString;
701732
}
702733

703734
JsVar *jsvNewFromString(const char *str) {
704735
// Create a var
705736
JsVar *first = jsvNewWithFlags(JSV_STRING_0);
706-
if (!first) {
707-
jsWarn("Unable to create string as not enough memory");
708-
return 0;
709-
}
737+
if (!first) return 0; // out of memory
710738
// Now we copy the string, but keep creating new jsVars if we go
711739
// over the end
712740
JsVar *var = jsvLockAgain(first);
@@ -725,7 +753,7 @@ JsVar *jsvNewFromString(const char *str) {
725753
if (*str) {
726754
JsVar *next = jsvNewWithFlags(JSV_STRING_EXT_0);
727755
if (!next) {
728-
jsWarn("Truncating string as not enough memory");
756+
// Truncating string as not enough memory
729757
jsvUnLock(var);
730758
return first;
731759
}
@@ -3123,6 +3151,8 @@ static void jsvGarbageCollectMarkUsed(JsVar *var) {
31233151

31243152
/** Run a garbage collection sweep - return true if things have been freed */
31253153
bool jsvGarbageCollect() {
3154+
if (isMemoryBusy) return false;
3155+
isMemoryBusy = true;
31263156
JsVarRef i;
31273157
// clear garbage collect flags
31283158
for (i=1;i<=jsVarsSize;i++) {
@@ -3224,7 +3254,7 @@ bool jsvGarbageCollect() {
32243254
* our fake 'firstVar' variable */
32253255
jsvSetNextSibling(lastEmpty, 0);
32263256
jsVarFirstEmpty = jsvGetNextSibling(&firstVar);
3227-
3257+
isMemoryBusy = false;
32283258
return freedSomething;
32293259
}
32303260

src/jsvar.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,7 @@ typedef struct {
202202
JsVarData varData;
203203

204204
/** the flags determine the type of the variable - int/double/string/etc. */
205-
JsVarFlags flags;
205+
volatile JsVarFlags flags;
206206
} PACKED_FLAGS __attribute__((aligned(4))) JsVar;
207207

208208
/* We have a few different types:

src/jswrap_espruino.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -610,6 +610,7 @@ JsVar *jswrap_espruino_getErrorFlags() {
610610
if (jsErrorFlags&JSERR_CALLBACK) jsvArrayPushAndUnLock(arr, jsvNewFromString("CALLBACK"));
611611
if (jsErrorFlags&JSERR_LOW_MEMORY) jsvArrayPushAndUnLock(arr, jsvNewFromString("LOW_MEMORY"));
612612
if (jsErrorFlags&JSERR_MEMORY) jsvArrayPushAndUnLock(arr, jsvNewFromString("MEMORY"));
613+
if (jsErrorFlags&JSERR_MEMORY_BUSY) jsvArrayPushAndUnLock(arr, jsvNewFromString("JSERR_MEMORY_BUSY"));
613614
jsErrorFlags = JSERR_NONE;
614615
return arr;
615616
}

0 commit comments

Comments
 (0)