xsCollectGarbage
(documented in XS in C. Or just use the debug
module, which provides it already.
great... how?
I am now confident there is a bug in Timer ... I think it deals with garbage collection and use of host memory, but was hoping to isolate further before sending your way. I can repo the Timer bug in just a few lines, but I need to jump to debugger and back to do it (that does a GC)
Timer fails for me with an xsGetHostData: invalid
in my product. I have it recreated as long as I do a debugger
statement at the end (I tried various Debug.gc()
calls but that didn't seem to exacerbate it), so not sure what else debugger
is doing that also occurs in my product (without using debugger
) but I'm out of ideas. Here is the fragment that causes the failure (run, hit breakpoint, and continue running to see the exception):
import Timer from 'timer';
const myTimer = Timer.set(() => {}, 300);
Timer.set(() => Timer.clear(myTimer), 100);
debugger;
Do you want me to make GitHub issues for this and/or the Proxy host function issues?
Timer.clear
ends up executing after the set expires.
undefined
though I believe that's not necessary w/clear)
Very odd. I'm 99.99% confident I am clean on Timer
usage. I have only two methods that ever call Timer
, they guard it, and I track a unique ID with each usage and trace it. I see exactly what I would expect - several timers created, one expires and when I try to clear a different (not yet expired, long delay) timer it fails with xsGetHostData: invalid
. HOWEVER ... this only happens when I proxy the class that contains the Timer
, not the Timer
itself. I've verified this
looks fine in the debugger prior to the failure (it has the ID as I'd expect, for example). I've tried to reproduce this in a small fragment without success. Does anything pop into your head as to why the xsGetHostData
error might happen when using a proxy to hold the Timer
object?
I'm looking now into adding decorators so I can mark that class as being disallowed from mocking (I hacked that in and it works) ... but it sure feels like a bug - likely in my code or perhaps something to deal with Host usage in Timer and Proxies? Crazy stuff, sorry. If you have no ideas given how abstract this is, no worries - but I'm burning a lot of hours so I thought I'd ask.
Timer.clear
that is not a timer object (a timer object which has been cleared -- which includes a one shot timer that has fired -- is considered "not a timer object"). So... when you hit that break, take a look at the argument to Timer.clear on the stack to see what it is.
gist
it, but gist.github.com
appears down. The following reproduces the failure - likely could reduce the proxy logic some but it hopefully will reproduce the failure (and not due to another stupid mistake on my part!)import Timer from 'timer';
class MyClass {
myTimer;
}
const handler = {
construct(target, argArray, newTarget) {
return new Proxy(Reflect.construct(target, argArray, newTarget), handler);
},
get(target, propertyKey, receiver) {
const value = Reflect.get(target, propertyKey, receiver);
if (propertyKey == 'prototype') return value;
const proxy = new Proxy(value, handler);
return proxy;
},
};
// choose one of the following sections - the first fails with Proxy, the second works without it
const ProxyMyClass = new Proxy(MyClass, handler);
const myProxyInstance = new ProxyMyClass();
// const myProxyInstance = new MyClass();
myProxyInstance.myTimer = Timer.set(() => {
myProxyInstance.myTimer = undefined;
trace('long time expired\n');
}, 300);
Timer.set(() => {
trace('short timer expired\n');
if (myProxyInstance.myTimer) Timer.clear(myProxyInstance.myTimer);
}, 100);
Timer.set(() => {
trace('done\n');
}, 2000);
Imagine you have a class with many objects inside it. You want to mock it, so you use a generic mocking service to do it:NewClass = mock(OldClass)
(or newObject = mock(new OldClass())
. That class that wraps the initial proxy, and as the handler executes it continues to proxy what it finds (get/construct/access).
No worries - I'll find a way around this. My issue - not Moddable's. Thanks for the help.
Array.prototype.push
. Internally to XS there are actually two kinds of host functions – a host object and a host primitive. The host primitive is more compact and less standard. As part of preparing the ROM image, the XS linker converts host objects to host primitives. That step is skipped when strip is disabled (which is why turning off strip allows your Proxy
to work). In most places, XS automatically and invisibly promotes a host primitive to a host object when needed (fxToInstance
). The special case to do that promotion isn't in the Proxy constructor, so it fails. We'll add that. Just FYI, a more focused workaround is to use Object.assign
which promotes the host primitive to a host object: new Proxy(Object.assign([].push), {})
.
Thanks! Since I can't tell which objects are host, I'd have to do that on every object, property, etc., which I'd prefer not to do. But it sounds like you found the fix?
I have @unmockable
working now and it's resolved my issue with Timer
- so I'm all good there! Getting close to have preload working on all modules (one big module left to go). Dependency injection and Mocking are working now (major rework to Mock, a bit of refactoring to Dependency Injection). Hopefully a few more days and I'll see what the new slot footprint looks like.
Mind if I ask a couple performance/memory questions?
1) If I have a method that needs to return an object, is there much benefit in slots/memory/fragmentation if I return a native object (return { prop: value }
) vs. use a class to contain the object (return newClass(value);
where the class provides the prop
property)?
2) Is it correct that if I have an object that is not preloaded with six properties, it will consume 7 slots (1 for the object, and 6 for the properties, or a total of 112 bytes)?
3) Likewise, if I have an array of 100 elements, it takes 100 slots? (And if the array has objects, we multiple the above with this ... or in this case, 100 elements * 6 slots = 600 slots = 9.6K?)
4) Would using CDV to encapsulate heavy usage objects (arrays of objects with many properties) drop the slot count to one per CDV buffer?
xsuse
example.
just pulled the public branch and did the "make install" for linux, got hit with the following errors.
make install -f simulator.mk
make[1]: Entering directory '/home/loki/Projects/moddable/build/makefiles/lin'
/home/loki/Projects/moddable/build/simulator/lin/main.c: In function ‘onApplicationOpen’:
/home/loki/Projects/moddable/build/simulator/lin/main.c:434:99: warning: ‘.xsa'’ directive output may be truncated writing 5 bytes into a region of size between 3 and 8193 [-Wformat-truncation=]
434 | snprintf(cmd, sizeof(cmd), "/bin/cp -p \'%s\' \'%s.xsa\'", path, gxConfigPath);
| ^~
/home/loki/Projects/moddable/build/simulator/lin/main.c:434:49: note: ‘snprintf’ output between 21 and 8211 bytes into a destination of size 8208
434 | snprintf(cmd, sizeof(cmd), "/bin/cp -p \'%s\' \'%s.xsa\'", path, gxConfigPath);
| ^~~~~~~~~~~~~~~~~~
/home/loki/Projects/moddable/build/simulator/lin/main.c:427:99: warning: ‘.so'’ directive output may be truncated writing 4 bytes into a region of size between 3 and 8193 [-Wformat-truncation=]
427 | snprintf(cmd, sizeof(cmd), "/bin/cp -p \'%s\' \'%s.so\'", path, gxConfigPath);
| ^~~
/home/loki/Projects/moddable/build/simulator/lin/main.c:427:49: note: ‘snprintf’ output between 20 and 8210 bytes into a destination of size 8208
427 | snprintf(cmd, sizeof(cmd), "/bin/cp -p \'%s\' \'%s.so\'", path, gxConfigPath);
| ^~~~~~~~~~~~~~~~~~~
Assembler messages:
Fatal error: can't create /home/loki/Projects/moddable/build/tmp/lin/debug/simulator/main.o: No such file or directory
make[1]: [simulator.mk:59: /home/loki/Projects/moddable/build/tmp/lin/debug/simulator/main.o] Error 1
make[1]: Leaving directory '/home/loki/Projects/moddable/build/makefiles/lin'
make: [makefile:66: install] Error 2