-
Notifications
You must be signed in to change notification settings - Fork 1.7k
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
hooks for custom control sequences (updated) #1853
Conversation
This fixes (at least partially) issue xtermjs#1176 "Add a way to plugin a custom control sequence handler".
…on." This reverts commit 0311563.
This reverts commit b689745.
This reverts commit 4d660de.
This re-implements addCsiHandler/addOscHandler to return an IDisposable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 LGTM. Lets wait for Tyriars response though.
Sorry Per, have to dismiss the review - imho there are still issues with the dispose
function.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like there are 2 possible memory leaks.
src/EscapeSequenceParser.ts
Outdated
private _linkHandler(handlers: object[], index: number, newCallback: object): IDisposable { | ||
const newHead: any = newCallback; | ||
newHead.nextHandler = handlers[index] as IHandlerLink; | ||
newHead.dispose = function (): void { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nextHandler
gets not freed anywhere possibly holding that handler forever (if the disposed handler gets not removed on caller side). Same with the handlers
object. Imho nulling both afterwards and conditionally exiting at the beginning when they not exist anymore (already disposed) solves this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"nextHandler gets not freed anywhere possibly holding that handler forever (if the disposed handler gets not removed on caller side)."
Right - but wouldn't that be a leak on the caller side? You really shouldn't keep a reference tofoo
after calling foo.dispose()
. We can null out nextHandler as a precaution, but it would only serve to paper over a leak in the caller, methinks:
if (cur === newHead) {
if (previous) { previous.nextHandler = cur.nextHandler; }
else { handlers[index] = cur.nextHandler; }
+ cur.nextHandler = null;
break;
}
"Same with the handlers object."
I don't think that is an issue. The handlers
object is this._csiHandlers
or this._oscHandlers
, which are both allocated in the constructor.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yepp thats basically a leak on caller side, but in JS ppl tend not to think about lifetime of an object at all. And we cannot control caller side here as its exposed to the public. Therefore I think we should take care over what we can control as a precaution. Both refs are likely to contain handlers of InputHandler
which itself contains a Terminal
ref. Terminal
is kinda the root of our "disposable object tree", thus forgetting to remove a disposed handler ref might prevent the whole object tree from GCing, which is far worse than just a leftover handler ref.
Now talking about dispose and GC - I think it is also flawed regarding the invocation on a higher tree object that takes care of the subtree objects (the default dispose
descent). I think EscapeSequenceParser.dispose
also would have to invoke dispose
on the added handlers (with nulling in place), otherwise InputHandler
will be kept bound in the added handlers. (InputHandler.dispose
itself nulls the terminal object, so at least the majority of objects will be released.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Additional note on that: From API perspective its a surprising side effect, that an added handler pulls in refs to the terminal object (through handlers
and nextHandler
). Its not obvious for ppl, they only see the interface that acts on parser states and input. Atm dispose
covers the "remove" thing, but not the ref releasing.
Btw the arrow function as added handler binds the parser object as this
, just to be able to call the fallback. Hmm. Thats not the case for handlers via set...
. Imho this needs a slightly different approach.
The only way I can see a leak that we can avoid is if there are two handlers A and B for the same index, with A added first, such that I'm not convinced this is a likely scenario, but it seems a good idea to null the nextHandler, regardless. "that an added handler pulls in refs to the terminal object (through handlers and nextHandler)" I don't see where the handler itself has a ref to the terminal object (unless the EscapeSequenceParser has a ref). However, the caller-supplied function is likely to have a reference. I added a commit 48ff841 for "just-in-case" cleanup. Is that ok? |
I created PerBothner#1 with some proposed changes:
On leaking memory, I think the nulling out that's in that PR should be sufficient, in the future we will probably do that less and less as it's a pain to deal with |
I have no objection to using arrays instead of linked list. That is certainly simpler and more readable. The downside is it might use slightly more memory (an extra array per handler index) and be slightly slower (especially in the simple cases when "add" hasn't been called). If you and jerch are happy, I'm happy. (I also have two more specific comments at PerBothner#1.) |
Move to array-based instead of linked list, simplify, add tests
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The dispose chain looks clean to me now (beside the handler array, but thats no biggie imho as it does not hold refs to the parser or any other object up to terminal).
Almost ready to ship, just the handler removal does not yet do the right thing imho.
@PerBothner Thx alot, hope it was not to bumpy to get it done. With this we have a solid base to custom CSI and OSC functionality through addons. Still not sure how to deal with terminal internals if needed by addons, but I think we can decide that when really needed by a custom addon. |
@PerBothner thanks for bearing with us 😃 |
You're welcome. I tend to be a bit of a perfectionist myself ... |
How do you calculate the string version of |
@sdegutis It works the way around - you have to register a hook that correctly matches the // hook into DECSET - defined as CSI ? P1;P2;...;Pn h
addCsiHandler({prefix: '?', final: 'h'}, params => {
// params contains [P1, P2, ..., Pn]
}); DECSET in particular has no intermediates. In general a CSI sequence follows this scheme:
Every value has byte restrictions to get parsed properly, see docs. Edit: Ok seems you are looking for a hook for a specific param value an CSI? Thats not possible, we do not split the sequences further, thus you have to iterate through params and handle the one you are interested in. Dont forget to return false to keep the default handler running (as its not a good idea to skip the default handler here). |
This is pull request #1813 is a separate branch as request.
That pull requst should probably be closed as a duplicate or something.
This pull request is still unnecessarily messy, as it includes commits that are just reverts of older commits. The actual real commits are 8ceea11 5af4626 8a5a032 6b65ebd .
Fixes #1176