Skip to content

Update roadmap #90

@demurgos

Description

@demurgos

Hi,
Last year I did some work on spawn-wrap on my fork to support hooking the Node inspector into Node processes deep in the process tree. I eventually built a library dedicated to this use case, but my spawn-wrap fork still had a lot of work put into it to fix some issues and provide new features. I would like to merge the changes so others can benefit from them. I started sending small PRs, this issue is intended to give the outline of the changes I made and discuss them in a larger scope.

There were many small changes that together improved the reliability of the lib and fixed various Windows issues: using safe path manipulation functions, escaping the values more explicitly, reducing the number of extra files (no settings.json), avoiding mutations of the user-supplied options object, etc. One of the intended goals is to reduce the amount of patches to Node internals. It does not remove all internal patches (see #89).

There were also some larger changes:

  • Provide an API that does not patches the internals. This allows to spawn concurrent sub-processes with different options without interference. This is achieved by creating a context (options + shims) and then using it to create a spawn function. This function rewrites its arguments to inject the wrapping logic and forwards the arguments to the native spawn function. The general idea is the following:

    // lib code
    function wrapSpawn(ctx) {
      return function(...args) {
        const wrappedArgs = munge(ctx, args);
        return cp.spawn(...newArgs);
      }
    }
    
    // user code
    const ctx = new SwContext(options);
    const wrappedFn = wrapSpawn(ctx);
    const child = wrappedFn("node", [...]);
    
    
    const ctx2 = new SwContext(options2);
    const wrappedFn2 = wrapSpawn(ctx2);
    const child2 = wrappedFn2("node", [...]);
  • Add static type checks with Typescript. The library is stable now so it would benefit from being annotated and checked. Using types helped me a lot to understand how spawn-wrap works: I coud annotate what were the expected values and what each function returned. As a bonus it enables better autocompletion and documentation for the consumers. I wrote a small post about it: it's definitely worth it for a stable lib.

  • The only breaking change I eventually introduced was a simplification of how the wrapper code is executed. I looked into all the dependent packages and how they use spawn-wrap. They fall into two categories: they either want to ensure some VM options are enabled or run some code before/after the user code. For the second case, the wrapper code often receives some config from the root process but the current design makes this use case hard (it involves pushing and poping args or env vars). Starting the user-code from the wrapper uses a runMain function exported by spawn-wrap, but this function does not work outside of the wrapper. I eventually settled on a design where the wrapper may export a function, the shim then calls it with a config object containing data from the parent process and a method to start the user code. See the migration section on my fork

  • Finally, the largest of all changes was adding support for dynamic arguments (Dynamically create child_process args. #47). This allows a very powerful abstraction were the root process can watch process creations at any depth in the process tree, modify their arguments, directly read their stdin/stdout/stderr, etc. This is built on top of the current static version using some IPC. Ultimately, I don't think it should be merged here: it is great but too complex. It could be published as a separate package depending on spawn-wrap for users needing this kind of control.

I opened a PR (#77) with all those changes, but it should not be merged: it's just to show you the code I ended up with. Instead, I am extracting the changes one by one and sending small PRs. This allows to discuss each change individually and catch errors (thanks for the reviews).

My first step was to update the requirements and remove legacy code. This is almost done. I'll send a PR soon to split the library into smaller modules so it's better to see the structure of the lib at a glance. At this point, the library should still be functionally the same: it would be a good point to do a release and check that the refactoring did not break anything. The main change would be the increased minimum Node version.

Once this is done, I'd like to merge new features: encapsulations with contexts and static type checks. This is also the step were I added most of the minor fixes.

Then, depending on the discussions, I'd propose the new API were the wrapper module exports a function called by the shim.

/cc @bcoe @JaKXz @coreyfarrell

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions