Repository navigation
fix: return the original promise from an instrumented method - #11
Open
johnnyhuirilef wants to merge 1 commit into
Open
johnnyhuirilef wants to merge 1 commit into
johnnyhuirilef wants to merge 1 commit into
Conversation
An instrumented method that returned a promise gave the caller a new promise, built with then().catch(). The caller lost every member of the original, like json() on a got request or cancel() on a cancelable promise. A Promise subclass also built extra instances through Symbol.species. The span now ends on a side branch of the original promise, and the original promise goes back to the caller as it is. This is the way OutgoingSpanRecorder.endWhenSettled already works. A throw while the span closes no longer reaches the caller. A rejection nobody handles no longer raises unhandledRejection.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
PR Checklist
PR Type
What is the current behavior?
Issue Number: N/A (issues are disabled on this repo)
Hi! 馃憢 With instrumentation on, a provider method that returns a promise with extra members hands the caller a different promise. The members are gone. A
gotrequest loses.json(),.text()and.buffer().execa10 loseskill()andstdout. A cancelable promise losescancel(), so the work keeps running.The cause is in
src/instrument/create-instance-decorator.instrument.ts, lines 148 to 150:return result.then(onReturnValue).catch(onError). Each call tothenorcatchbuilds a new promise. The caller gets the last one. A Promise subclass also builds two extra instances throughSymbol.species.OutgoingSpanRecorder.endWhenSettledinsrc/outgoing/outgoing-span.recorder.tsalready solves this for outgoing spans. It watches the promise on a side branch and returns the promise untouched. Its comment says the caller's chain must see the same value and the same rejection as without the span.I ran the repro on 0.3.7:
Until a release has the fix,
createObserveModule({ skipInstrumentation: (instance) => instance instanceof DataService })keeps such a provider working.What is the new behavior?
instanceof Promise. Other thenables are not touched.Does this PR introduce a breaking change?
No API changes. Two behaviors differ. First, it makes the same trade as
endWhenSettled. The side branch marks the original promise as handled, so a caller that drops a rejecting promise no longer raisesunhandledRejection. Before, the new chain was the one left unhandled, so the process saw the event. Re-throwing on the side branch brings the event back, but it also raises it for callers that do handle the rejection. Second, a throw while the span closes used to reject the caller and close the span twice. Now the caller keeps the real value or rejection.Other information
The unit specs call the decorator on a provider method that returns a promise. They check:
toBe), with itscancelmember intact, and the span ends with no error.The new int spec boots a real Nest app. A provider returns a promise with a
json()member, andGET /upstreamcalls it and returns 200. It returns 500 onmaster.The specs for identity and for the subclass fail on
masterand pass with the fix. I also changed the fix in five ways. I went back to the old chain, returned the side branch, re-threw on the side branch, removed the guard aroundonReturnValue, and removed theonErrorcall. A spec or an unhandled rejection failed every time.Three other wrappers also return a new promise:
operation-trace.registry.ts(line 873, behind theasynccreateSpan),job-trace-runner.ts(line 330) andws-observe-agent.service.ts(line 222, withfinally). I left them out to keep this PR on one wrapper. I am happy to open a follow-up for them. 馃檪