Skip to content

Conversation

@JPeer264
Copy link
Member

This is a follow up to #16681.

Problem

The waitUntil could take longer than the actual request is taking, which is actually a good thing and wanted by Cloudflare. By definition of our implementation we are ending the root span at the end of the request, while the promise inside waitUntil could still generate events. These events are only send while the root span is still active ("Event 1" in the mermaid diagram), but are not send anymore when the root span ended ("Event 2") and are basically dropped.

sequenceDiagram
    participant R as Request
    participant W as waitUntil
    participant S as Sentry Spans

    Note over R: Request starts
    activate R
    
    R->>S: Root span starts
    activate S
    
    Note over R,W: User calls ctx.waitUntil(promise)
    R->>W: Promise created
    activate W
    
    W->>S: Event 1 ✓
    Note over S: Captured (span still active)
    
    Note over R: Request handler returns
    deactivate R
    
    R->>S: Root span ends
    deactivate S
    
    Note over W: waitUntil continues running...
    
    W->>S: Event 2 ✗
    Note over S: LOST!<br/>Span already ended
    
    W->>W: waitUntil completes
    deactivate W                          
Loading

Solution

Note: I think that span streaming (which is coming soon) could fix that problem 🤞

While the solution is not perfect, it is mitigating the problem. To mitigate it we add a new transaction for the waitUntil, which means it is free of the root span duration, but it still has the information of the trace and its span parent.

sequenceDiagram
    participant R as Request
    participant W as waitUntil
    participant S1 as Root Span
    participant S2 as waitUntil Transaction

    Note over R: Request starts
    activate R
    
    R->>S1: Root span starts
    activate S1
    
    Note over R,W: User calls ctx.waitUntil(promise)
    R->>W: Promise created
    activate W
    
    W->>S2: waitUntil transaction starts
    activate S2
    
    W->>S2: Event 1 ✓
    
    Note over R: Request handler returns
    deactivate R
    
    R->>S1: Root span ends
    deactivate S1
    
    Note over W: waitUntil continues running...
    
    W->>S2: Event 2 ✓
    Note over S2: Captured!<br/>waitUntil transaction still active
    
    W->>S2: waitUntil transaction ends
    deactivate S2
    
    W->>W: waitUntil completes
    deactivate W
Loading

Side effects

That actually causes one slight problem, where I don't have the solution for, except writing documentation.

When there is a manual sentry span added inside a waitUntil it will have the wrong parent span, since the parent is the same as the one of the request. Since by the time of the execution of asyncFn() the parent will be request, and not asyncFn. However, this can be solved when the user adds a forceTransaction: true manually inside the Sentry.startSpan, since then it will decouple again and will have the correct parent span.

const asyncFn = await () => {
  await somethingAsync();
  
  // this will have the wrong parent span
  // unless `forceTransaction: true` is given
  return Sentry.startSpan({}, async () => {
    await moreAsyncFunctionality();
  });
};

waitUntil(asyncFn())

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants