Work on my FMJS is coming along. I've been trying things out, having fun looking up symbols at runtime, disassembling code, learning a bit about strict aliasing, and basically organizing things in a way that works for me.
Functions, object creation and method calls, creating, modifying, and passing around structs all work now. And I've got a little test suite using Xcode's unit test framework that's been a wonderful way to develop FMJS and make sure nothing breaks with my changes.
The biggest problem I've been running into is getting JavaScriptCore to run garbage collection in a reasonable time frame. The API for doing this, JSGarbageCollect
, doesn't actually clean up unreferenced objects when you ask it to. Instead, it schedules the cleanup to happen sometime in the future. And it doesn't happen soon enough for my needs.
I have a test which allocates objects, and waits to see if they go away after cleaning up the JS runtime. They weren't being deallocated. I'm not sure what made me think of it, but I setup a runloop that just spins around until eventually JSCore decides to do it's thing between 3 and 70 seconds later. And then the test passes. But that's an extra 3-70 seconds of just sitting around waiting for my objects to be deallocated.
OK, let's print out the runloop and see what's going on:
0 : <CFRunLoopTimer 0x10a2b68b0 [0x7fffa8fe68e0]>{valid = Yes, firing = Yes, interval = 315360000, tolerance = 0, next fire date = 559929410 (-5.33140898 @ 151250457040796), callout = _ZN3JSC14JSRunLoopTimer20timerDidFireCallbackEP16__CFRunLoopTimerPv (0x7fff5537e6d0 / 0x7fff5537e6d0) (/System/Library/Frameworks/JavaScriptCore.framework/Versions/A/JavaScriptCore), context = <CFRunLoopTimer context 0x10a7009e0>}
OK using that information I was able to find the source code on GitHub that sets up the timers for the garbage collection. In the code a JS CFRunLoopTimer is created, with an interval of a decade (60 * 60 * 24 * 365 * 10) and a firedate of a decade from when it is created.
Huh.
OK, so that explains why it's not called in a reasonable time frame. But, 10 years isn't the same as 70 seconds. What's going on? Why is it being called before a 10 year time frame?
I never promised answers. And I have no idea. Runloops are magic I guess.
But, while poking around a bit more in JSCore's source, I did find this private method:
JS_EXPORT void JSSynchronousGarbageCollectForDebugging(JSContextRef);
Which does exactly what it says on the box. No runloops needed.
Obviously, this is a private API. And obviously you wouldn't want to use this in production code. But in a unit test with the right protections around it, I think it's an OK call.
But I still need to figure out how to work around the delayed garbage collection. And I suppose I should write up a radar for synchronous garbage collection.