Search code examples
windowsvscode-extensionssynchronousvscode-api

Vscode-specific issue with node-xmlhttprequest-sync: crashes Extension host in Windows; error is caught in MacOS; works as expected in Linux


I am developing a vscode extension to wrap some functionality from an external API (ee in the examples below). Some of the functions in this external library can be called either synchronously:

console.log(ee.String("Hello world.").getInfo());

or can be provided with a callback function:

ee.String("Hello world").getInfo(console.log);

In linux (e.g. from WSL), both of these work as intended, but in windows, the synchronous call freezes the command completely. So for example, the following code within the command:

ee.String("Hello world from callback").getInfo(console.log);
//console.log(ee.String("").getInfo()); // Commented

will work as intended and I will see the "Hello world from callback" message in the console. However, if I uncomment the second line, the command gets stuck and I don't even get the result from the callback, let alone the output of the synchronous call.

The following minimal reproducible example works as intended using node through the command line, both in windows and linux:

var ee = require("@google/earthengine");
var token=process.env.EETOKEN;
var project=process.env.EEPROJECT;
ee.data.setAuthToken('', 'Bearer', token, 3600, [], 
()=>{
    ee.initialize(null, null, 
    ()=>{
        function print(a){
            console.log(a);
        }
        ee.String("Hello from callback")
        .getInfo(print);
        console.log(
        ee.String("Hello from synchronous request")
        .getInfo()
        );
    }, 
    undefined, null, project
    );
    }, false);
$ node temp.js 
Hello from synchronous request
Hello from callback

so I am wondering why this fails from within my extension command and only in windows, except in Linux. Is this a bug, or am I missing something?

EDIT:

In MacOS, the extension actually catches an error, which is:

Error: EROFS: read-only file system, open '.node-xmlhttprequest-sync-32376'

which led me to find this somewhat related unanswered question:

https://gis.stackexchange.com/questions/462901/gee-in-a-nextjs-monorepo-on-vercel

but not much information elsewhere. I also opened this issue in vscode.

Any clues?


Solution

  • I finally figured this out.. it's the result of a combination of factors:

    • The .getInfo() method uses the xmlhttprequest.

    • For synchronous requests, the xmltthprequest library does the following:

      1. Create an empty file called .node-xmlhttprequest-sync- + the process id.
      2. Spawn a node process with the -e option to execute a string. This string is basically a series of commands that make the request asynchronously but when the request is complete (either with a response or an error), it will write a file called .node-xmlhttprequest-content- + process id containing the response, and it will then delete the .node-xmlhttprequest-sync file.
      3. After spawning the process from 2., there is a while loop that does nothing while the .node-xmlhttprequest-sync exists:
      while(fs.existsSync(syncFile)){
         // Wait while the sync file is empty
      }
      
      1. Finally, when the .node-xmlhttprequest-sync file doesn't exist anymore, continue to read the .node-xmlhttprequest-content file and return the response/error.
    • The node process (step 2) is spawned using:

      var syncProc = spawn(process.argv[0], ["-e", execString]);
      

    This line is the root of the issue. When I ran the code using node, then evidently process.argv[0] is the node executable. However, in vscode, process.argv[0] depends whether you are running vscode directly or within a remote environment.

    When running vscode directly, e.g. in Windows, process.argv[0] could be something like:

    C:\Users\username\AppData\Local\Programs\Microsoft VS Code\Code.exe
    

    So the spawn command is trying to spawn something like:

    C:\Users\username\AppData\Local\Programs\Microsoft VS Code\Code.exe -e execString
    

    which obviously will not work as intended by xmlhttprequest, and thus the syncFile never gets deleted, which consequently makes the while loop in step 3 stay stuck. This is why the Extension Host crashes!

    But why did it work in Linux? Turns out it wasn't really any Linux magic, but the fact that I was running vscode with the remote extension. When I connected to a Linux remote machine, e.g. using either SSH or WSL, then process.argv[0] is:

    ~/.vscode-server/bin/1a5daa3a0231a0fbba4f14db7ec463cf99d7768e/node
    

    i.e., vscode includes a node executable in the remote server. Thus, the xmlhttprequest worked in this case.