Skip to content
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

fix: pass through data when network request completes #1696

Merged
merged 3 commits into from
Mar 5, 2024

Conversation

ganeshnj
Copy link
Contributor

@ganeshnj ganeshnj commented Feb 22, 2024

What and why?

Fixes #1680

How?

This PR mimics the old behavior of the instrumentation.

When completion handler is provided in URLSession APIs, urlSession(_:dataTask:didReceive:) is not called, which means we can't buffer the data and in the called the Resource Attribute Provider.

This is not only true for the completion handler based APIs but also true for async-await APIs.

Meanwhile we use urlSession(_:task:didFinishCollecting:) to mark the completion of the network operation which is the only reliable API at this moment. We use this to mark completion for async-await APIs.

How did the regression happen?

For some APIs, both urlSession(_:task:didFinishCollecting:) and urlSession(_:task:didCompleteWithError:) are called. But there is not specific order guaranteed which one comes first, ie the one called first, marks the end of the network operation. In the breaking change, urlSession(_:task:didFinishCollecting:) and given urlSession(_:dataTask:didReceive:) was not called either, Resource Attribute Provider executes with nil data.

How does this PR fix it?

With this PR we make sure, if the API has a completion handler given, we are not going to use urlSession(_:task:didFinishCollecting:) as end of operation because we are sure urlSession(_:task:didCompleteWithError:) will be called.

Along side, when the operation finishes, we also call handler for registering the data when the operation completes. The APIs guarantees from behavior that either urlSession(_:dataTask:didReceive:) is called or the compeltion handler, hence we don't register data two times.

Do we support async-await?

No, because they don't have completion handler, so experience remain broken with them.

Tested locally on

  • iOS 12.4
  • iOS 13.0
  • iOS 14.5
  • iOS 15.5

Review checklist

  • Feature or bugfix MUST have appropriate tests (unit, integration)
  • Make sure each commit and the PR mention the Issue number or JIRA reference
  • Add CHANGELOG entry for user facing changes

Custom CI job configuration (optional)

  • Run unit tests for Core, RUM, Trace, Logs, CR and WVT
  • Run unit tests for Session Replay
  • Run integration tests
  • Run smoke tests
  • Run tests for tools/

@datadog-datadog-prod-us1
Copy link

Datadog Report

Branch report: ganeshnj/fix/resource-attributes-provider
Commit report: adcc669
Test service: dd-sdk-ios

❌ 3 Failed (0 Known Flaky), 1202 Passed, 0 Skipped, 1m 37.56s Wall Time
🔻 Test Sessions change in coverage: 2 decreased, 3 increased, 1 no change

❌ Failed Tests (3)

  • testGivenURLSessionWithCustomDelegate_whenUsingAsyncDataForURLRequest_itNotifiesInterceptor - NetworkInstrumentationFeatureTests - Details

    Expand for error
     Assertion Failure: Asynchronous wait failed: Exceeded timeout of 5 seconds, with unfulfilled expectations: "Notify intercepion did complete".
    
  • testGivenURLSessionWithCustomDelegate_whenUsingAsyncDataFromURL_itNotifiesInterceptor - NetworkInstrumentationFeatureTests - Details

    Expand for error
     Assertion Failure: Asynchronous wait failed: Exceeded timeout of 5 seconds, with unfulfilled expectations: "Notify intercepion did complete".
    
  • testGivenURLSessionWithCustomDelegate_whenUsingAsyncData_itPassesAllValuesToTheInterceptor - NetworkInstrumentationFeatureTests - Details

    Expand for error
     
     Assertion Failure: Asynchronous wait failed: Exceeded timeout of 5 seconds, with unfulfilled expectations: "Notify intercepion did complete".
     Assertion Failure at NetworkInstrumentationFeatureTests.swift:358: XCTAssertEqual failed: ("nil") is not equal to ("Optional("some error")")
     Assertion Failure at NetworkInstrumentationFeatureTests.swift:358: XCTAssertEqual failed: ("nil") is not equal to ("Optional("some error")")
    

🔻 Code Coverage Decreases vs Default Branch (2)

  • test DatadogRUMTests iOS 79.30% (-0.24%) - Details
  • test DatadogInternalTests iOS 81.21% (-0.05%) - Details

@ganeshnj ganeshnj force-pushed the ganeshnj/fix/resource-attributes-provider branch from adcc669 to 7e761a8 Compare February 23, 2024 16:23
@ganeshnj ganeshnj force-pushed the ganeshnj/fix/resource-attributes-provider branch from 7e761a8 to 50bf3f5 Compare February 23, 2024 16:49
@ganeshnj ganeshnj marked this pull request as ready for review February 26, 2024 12:30
@ganeshnj ganeshnj requested review from a team as code owners February 26, 2024 12:30
Copy link
Collaborator

@ncreated ncreated left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

blocking/ On top of in-line comments, I think we're missing the fact that tasks are pooled in URLSession and may be reused for succeeding operations. That means the task state set with task.dd.hasCompletion must be reset before putting the task back to pool.

Comment on lines +224 to +225
/// Note: This is not supported for async-await APIs.
///
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question/ Meaning that we don't fully solve #1680 as it was originally reported for async/await APIs. We may need to update the PR description or add more details to the issue, otherwise it will be closed automatically with the merge.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For sure, this is regression fix. There is no path forward I could see which can solve this issue for async-await APIs like I mentioned in the description of the PR.

Yes, we are not going to close the issue but update it with what we did.

@ganeshnj
Copy link
Contributor Author

ganeshnj commented Mar 4, 2024

blocking/ On top of in-line comments, I think we're missing the fact that tasks are pooled in URLSession and may be reused for succeeding operations. That means the task state set with task.dd.hasCompletion must be reset before putting the task back to pool.

What do you mean here? I don't expect same instance of URLSessionTask being used again. Rather I expect the connection to be used.

…grationTests.swift

Co-authored-by: Maciek Grzybowski <maciek.grzybowski@datadoghq.com>
Copy link
Collaborator

@ncreated ncreated left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ganeshnj I was wrong, sorry. I always thought that URLSessionTask is one of these few Apple objects that are reused through pool 🤔💭, like UITouch or table cells. Interesting 🙂. There is indeed no note on that in URLSession docs, and I don't observe this behavior in tests.

Looks good 👍. Please make sure it all works correct iOS 13-17, as we have no automated coverage at this moment.

Copy link
Contributor

@maciejburda maciejburda left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good. Some minor nits to consider :)

@@ -139,6 +139,7 @@ open class DatadogURLSessionDelegate: NSObject, URLSessionDataDelegate {
try swizzler.swizzle(
interceptCompletionHandler: { [weak self] task, _, error in
self?.interceptor?.task(task, didCompleteWithError: error)
}, didReceive: { _, _ in
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: didReceive param could be optional with default nil instead

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah but not worth the refactor.

Comment on lines +43 to +47
if newValue {
objc_setAssociatedObject(type, &hasCompletionKey, true, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
} else {
objc_setAssociatedObject(type, &hasCompletionKey, nil, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there any benefit to clear it on false? Maybe that is enough:

Suggested change
if newValue {
objc_setAssociatedObject(type, &hasCompletionKey, true, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
} else {
objc_setAssociatedObject(type, &hasCompletionKey, nil, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
objc_setAssociatedObject(type, &hasCompletionKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)

not a big deal - just less code to read

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

more of personal preference, I don't think we would gain or lose either way.

@ganeshnj ganeshnj merged commit f1f3d40 into develop Mar 5, 2024
8 checks passed
@ganeshnj ganeshnj deleted the ganeshnj/fix/resource-attributes-provider branch March 5, 2024 17:58
@ncreated ncreated mentioned this pull request Mar 12, 2024
8 tasks
@maciejburda maciejburda mentioned this pull request Mar 19, 2024
8 tasks
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.

RUM.ResourceAttributesProvider not providing data information in URLSession tracking
3 participants