-
Notifications
You must be signed in to change notification settings - Fork 29.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
AsyncLocalStorage
losing state when used with net.Server
and repl.REPLServer
#37866
Comments
The document says
I'm not sure that calling cc @nodejs/async_hooks |
@iamFIREcracker I tried your sample with nodejs 14.16.0 and it worked. Could you try with an up to date node version? |
No luck, it's still broken for me:
For the first snippet, the REPL one, from the remote client's terminal:
For the second snippet, the one without the REPL, the one with
I read through the #33723 thread, and noticed that if I create an instance of net
.createServer(function (socket) {
ctx.run((reqId += 1), () => {
const res = new asyncHooks.AsyncResource("REPL");
debug("Connected", ctx.getStore());
socket.on("data", (data) => {
res.runInAsyncScope(
() => {
debug(`[${ctx.getStore()}]`, "on-data", data.toString());
},
this,
data
);
});
});
})
.listen(8081);
debug("Listening on port 8081"); on the server's stdout:
I quickly hacked the first snippet again, the REPL one, and implemented a man-in-the-middle solution where the socket's input is pumped into the REPL's stdin from inside an net
.createServer(function (socket) {
ctx.run((reqId += 1), () => {
const res = new asyncHooks.AsyncResource("REPL");
const repl = require("repl").start({ input: null, output: null });
socket.on("data", (data) => {
res.runInAsyncScope(
() => {
repl.input.emit("data", data);
},
this,
data
);
});
repl.context.ctx = ctx;
debug("Created repl", ctx.getStore());
});
})
.listen(8081);
debug("Listening on port 8081"); If I run this I get:
Clearly the wiring left something to be desired, but at least the context was not lost. I guess I can work-around that (eventemitter-asyncresource maybe?!), but I wonder if it wasn't somehow expected for the created REPL to be bound to the async context of the request. |
There's an official way to integrate with |
I'm still not able to reproduce this. Maybe because I'm not on a mac (tried windows native, windows wsl and linux). |
I can reproduce with a mac.
|
Since when is there syntax highlighting in issue titles 😮 ? |
I can reproduce the issue in MacOS. I did a bit step-into trick in the code snippet, and found that Lines 334 to 339 in 98ff5ac
IMO, the
In the use case here, I think A side note: One thing is interesting here. Is the issue can only reproduced in MacOS? It may be indicating semantic discrepancy btw platforms (darwin vs linux/windows)? Please correct me if I'm wrong.😊 |
Sorry, seems I mixed something up during my testing. Can reproduce now also on windows. |
@targos - I was playing with that earlier, but I could not quite wrap my head around that API, and as a result I could not make it work, i.e. I could not get ahold of the Note: the only reason why I mentioned
@Ayase-252 - I think that perfectly describes what's going on. However, what I am not 100% sure about is whether that's the expected behavior or not; in this case I kind of expect the REPL to evaluate expressions in the same execution context the REPL itself was originally created on, but I totally understand if there are reasons as to why this cannot be the default behavior (e.g. performance?!). So, speaking of work-arounds, what options do I have? Do I have to monkey-patch that |
The socket is created before outside of your async scope therefore it's clearly not bound to it. You could try to use a const { Transform } = require("stream");
class MyTransformStream extends Transform {
constructor(options) {
super(options);
this._res = new asyncHooks.AsyncResource("MyTransformStream");
}
_transform(chunk, encoding, cb) {
this._res.runInAsyncScope(cb, null, null, chunk);
}
} and then in your sample: const myStream = new MyTransformStream();
var repl = require("repl").start({
input: socket.pipe(myStream), // created within your async scope so ALS propagation works
output: socket,
}); |
Alright, I think I figured a way to make this work by using a combination of net
.createServer(function (socket) {
ctx.run((reqId += 1), () => {
const onOriginal = socket.on;
socket.on = function onHooked(data, callback) {
let callbackHooked = asyncHooks.AsyncResource.bind(callback, "REPL");
// The result of `AsyncResource.bind` is a function expecting
// its first argument to be `thisArg`, so to properly finish up
// the wiring we need to bind it before actually using the transformed
// callback
callbackHooked = callbackHooked.bind(this, this);
return onOriginal.apply(socket, [data, callbackHooked]);
};
const repl = require("repl").start({ input: socket, output: socket });
repl.context.ctx = ctx;
debug("Created repl", ctx.getStore());
});
})
.listen(8081);
debug("Listening on port 8081"); And with this, I am sure there might be better and more idiomatic ways of dealing with this, but at least it unblocks me for now. |
@iamFIREcracker Patching |
Totally! When I replied yesterday I had somehow missed your suggestion about using Alright, I am not sure if there is any intention of changing In the meanwhile, thanks everybody for the help! |
I don't think we should change |
Just had this with 14.17.1
|
@pozylon I doubt that this is related to this issue. I recommend to create a new one but please add more info like how to reproduce this. Optimum would be a reproducer which requires no additional modules. |
v14.5.0
async_hooks
What steps will reproduce the bug?
I am poking around a tiny wrapper around
repl
to make it possible for someone interact with a running Node.js instance, remotely, but when I tried to useAsyncLocalStorage
to attach some state to the active session, such state gets gets lost as soon as theREPLServer
takes over.Save the following into a file:
and run it:
Now from a different terminal, connect to port
8081
to get your remote REPL:What is the expected behavior / what do you see instead?
Accessing
ctx
(well, its content) from the REPL should return the ID of the REPL itself (i.e. an increasing number), butundefined
is returned instead:Additional information
I suspect it might have something to do with the underlying socket or
EventEmitter
, because when I try to access the content ofctx
from within asocket.on('data'...)
block, I still getundefined
.Hope this helps -- let me know if there is anything else I could help you with.
The text was updated successfully, but these errors were encountered: