Where communities thrive


  • Join over 1.5M+ people
  • Join over 100K+ communities
  • Free without limits
  • Create your own community
People
Activity
    Peter Hoddie
    @phoddie
    (The first argument to most of the Reflect functions is the this for the call.)
    Chris
    @cmidgley
    Yep. Interesting that it works on Node (though it is often more tolerant of mistakes). I'm sure it's me somewhere...
    Chris
    @cmidgley

    Hey Peter - I'm still having issues with Proxy on a host function. I've resolved the this problem, but this simple one-liner fails:

    new Proxy([].push, {});

    throws this exception:

    # Exception: Proxy: target is no object!

    Thoughts?

    Peter Hoddie
    @phoddie
    From just looking at the code, I'm not sure. Will need to look deeper. Thanks for the example.
    Chris
    @cmidgley
    Thanks for looking into it.
    Peter Hoddie
    @phoddie
    Tried it in my usual test app. There's no exception. Is there something about the environment that's relevant?
    Chris
    @cmidgley
    Interesting. I'm using stock Moddable (updated a few days ago), running in the examples / helloworld setup.
    Targeting esp32/heltec_lora_32, with debug (-d), building on Linux.
    I'll do some playing around.
    Peter Hoddie
    @phoddie
    FWIW – this test script in helloworld traces "1" as expected on macOS and ESP8266 (much faster to build and deploy than ESP32, should be identical behavior):
    let p = new Proxy([].push, {});
    let a = [];
    p.call(a, 1);
    trace(a[0], "\n");
    Peter Hoddie
    @phoddie
    (ESP32 build finished. Same result.) xsbug shows the target of the proxy as a frozen host function:
    Screen Shot 2022-05-16 at 9.51.37 AM.png
    Chris
    @cmidgley
    Odd ... but thanks. I'm trying to get a Windows environment up and running again and will try there.
    Peter Hoddie
    @phoddie
    It would be very strange if the host OS for the build triggered this, but there is some expected variable at play. Please let me know what you find.
    Peter Hoddie
    @phoddie
    Ha. I found a way to reproduce it. I have strip turned off in my build. If you add "strip": [] to your helloworld manifest, I think the problem will go away. Not sure why it matters yet...
    Chris
    @cmidgley
    Yeah, I don't think that's it. I just wanted a totally different environment and Windows is what I have.
    Interesting!
    Peter Hoddie
    @phoddie
    I kind of see what's happening, but the right solution is not obvious. That will take some time. I think you have a workaround for now though.
    Chris
    @cmidgley
    I'm trying the workaround now... thanks!
    Yes - that appears to work around the issue (no longer using strip). Thanks!!
    Peter Hoddie
    @phoddie
    Excellent. We'll looking into a proper fix, but at least you are unblocked. Nice find. It is an obscure combination to get to that.
    Chris
    @cmidgley
    I'm loving the fact that Moddable is real JS with insane capabilities... which I'm leveraging to the tilt! Up next may be a bug in Timer... working to isolate in a small module. Hopefully it's just something I'm doing wrong!
    Chris
    @cmidgley
    Nevermind on timer! It's something I'm STILL doing to mess up this in my proxy handling. One day at a time...
    Peter Hoddie
    @phoddie
    ...glad you are having fun. ;) Insane is a word that often comes to mind when talking about Proxy (...which is incredibly useful). You are uncovering some interesting bugs around the edges.
    Chris
    @cmidgley
    Peter - I know ... really strange request ... but is there a way to force a garbage collection to run on-demand from JS?
    Peter Hoddie
    @phoddie
    Yes.
    Create a binding for xsCollectGarbage (documented in XS in C. Or just use the debug module, which provides it already.
    Chris
    @cmidgley

    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)

    Chris
    @cmidgley

    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?

    Peter Hoddie
    @phoddie
    The thing that debugger is changing is time. The code is working as intended, but I can see how that can be confusing here. I can explain later.
    Chris
    @cmidgley
    No need - I see the silly mistake I made here (clearing myTimer after it expired). Adding a guard on that fixed it for this test (sadly, the same guard was already in my main code ... but I'll figure it out!)
    Peter Hoddie
    @phoddie
    Right. The thing i... you would the second timer (100ms) to execute before the first timer (300ms). But when you stop in the debugger, they both end up being eligible to fire at the same time. The timer implementation doesn't guarantee the order of callbacks in the case, so here Timer.clear ends up executing after the set expires.
    Chris
    @cmidgley
    Yes, totally makes sense and I had that in the main code. I just removed too much in my test! I'm working it...
    Peter Hoddie
    @phoddie
    The timer code could sort the list in order of expire time. That's more bookkeeping to get right though.
    Chris
    @cmidgley
    I use a simple guard - set the timer ID to undefined when it expires (before doing the callback) and also when clear is called.
    (and check for undefined though I believe that's not necessary w/clear)
    Peter Hoddie
    @phoddie
    RIght, I always do it that way. Timer.clear is safe to call with undefined. It will just ignore the call silently. Passing it an invalid timer (one that has been cleared, for example), will throw (because that seems like a bad mistake).
    Chris
    @cmidgley

    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.

    Peter Hoddie
    @phoddie
    That error happens when you pass an object to 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.
    Chris
    @cmidgley

    It's a Timer object - as best I can tell, as it says it is "(host) C data" which is what a Timer looks like otherwise.

    I have just gotten it to reproduce! When it's wrapped in a proxy it does this.

    Chris
    @cmidgley
    I would 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);
    (and now gist is back up, oh well)
    Peter Hoddie
    @phoddie
    You are proxying the host object!
    Chris
    @cmidgley
    What do you mean by "host object"? The class MyClass?
    Peter Hoddie
    @phoddie
    No. The timer. The handler.get function wraps every returned value in a proxy, which includes myTimer. That won't work, since there's no proxy trap (Reflect.*) for host data.
    Chris
    @cmidgley

    I see - I didn't know that about host data (and therefore Timer). I misunderstood from earlier that I could wrap host functions and didn't realize that host data was different!

    Any way to detect that condition inside the proxy handler?

    Peter Hoddie
    @phoddie
    You end up passing a Proxy instance to Timer.clear. That fails because it isn't a Timer.
    You can wrap host functions but the object used to retrieve the host data needs to be a host object.
    Chris
    @cmidgley
    OK. I do recursive proxy (optionally) and that has no understanding of what it is doing a proxy on. I'm looking now into possibly using typescript decorators.
    Peter Hoddie
    @phoddie
    I understand this code is an excerpt so the big picture is lost. I can't really imagine how to design a solution because I don't understand what this is trying to do.
    Chris
    @cmidgley

    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.