Skip to content

fix android http cancelation - #142

Open
tx3stn wants to merge 6 commits into
Snapchat:mainfrom
tx3stn:fix-android-cancelation
Open

fix android http cancelation#142
tx3stn wants to merge 6 commits into
Snapchat:mainfrom
tx3stn:fix-android-cancelation

Conversation

@tx3stn

@tx3stn tx3stn commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

Description

Android DefaultHTTPRequestManager diverges from the iOS/macOS defaults in two ways:

  1. Cancellation does nothing. HTTPRequestTask.cancel() nulls the completion but never touches the connection, so the transfer runs to completion and the result is thrown away.
    iOS actually cancels the NSURLSessionTask, so it works there.

  2. Requests are serial. The executor is ThreadPoolExecutor(0, 1, …). iOS uses NSURLSession.sharedSession, which is concurrent by default.

So a cancelled request neither stops nor frees the only worker, and everything else queues behind it until it finishes on its own.

Changes

  • cancel() disconnects the connection (a request cancelled while queued never opens a connection).
  • Pool sized to 4, matching NSURLSession.HTTPMaximumConnectionsPerHost (so Android behaviour is consistent with iOS)
  • Added unit tests for these and evrything with bazel test //valdi:test_java --test_output=errors is passing.

Type of Change

  • Bug fix (non-breaking change that fixes an issue)
  • Documentation improvement
  • Performance optimization
  • Test improvement
  • Other (please describe)

Testing

  • Tests pass locally (bazel test //...)
  • Added/updated tests for changes (if applicable)
  • Tested on multiple platforms (iOS/Android/Web/macOS as applicable)
  • Manual testing performed (describe below)

Testing Details

Checklist

  • Code follows project style guidelines
  • Documentation updated (if needed)
  • No breaking changes (or documented in description)
  • Commit messages follow conventional format
  • No secrets, API keys, or internal URLs included

Related Issues

Additional Context

@github-actions

github-actions Bot commented Aug 8, 2026

Copy link
Copy Markdown

⚠️ Bazel & CI Test Results

Test Suite Result
Snapshot Tests ✅ success
API Surface Check ✅ success
valdi_web Integration Test ❌ failure
macOS: C++ & Platform Tests ✅ success
Valdi Smoke Tests ❌ failure
Linux: Build Compiler ✅ success
Linux: Build & Export ✅ success
Linux: C++ Tests ✅ success
Linux: Registry Validation ✅ success
Linux: Hotreload Smoke ✅ success
Linux: Module Tests ✅ success

Some tests failed. Please check the workflow logs for details.

🚀 Bazel remote cache is now enabled - future builds will be faster!

Workflow: Valdi CI

@tx3stn

tx3stn commented Aug 8, 2026

Copy link
Copy Markdown
Contributor Author

failing job appears to be a timeout, not related to my changes, but I don't have permissions to re-run.

class DefaultHTTPRequestManager(context: Context): HTTPRequestManager() {
class DefaultHTTPRequestManager(
context: Context,
private val openConnection: (URL) -> URLConnection = { it.openConnection() },

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

It looks like adding this defaulted Kotlin parameter removes the existing DefaultHTTPRequestManager(Context) constructor from the published JVM ABI.

I double-checked the compiled before/after classes with javap. The PR artifact only has (Context, Function1) and the synthetic default-argument constructor. a reflection test for (Context) fails with NoSuchMethodException. The in-repo Kotlin caller recompiles successfully, but external Java callers and binaries linked against the existing constructor can break.

Would it make sense to preserve the public Context constructor and keep the injectable connection factory behind an internal test seam?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

good catch! gated behind VisibleForTesting.
ran javap -p -cp bazel-bin/valdi/valdi_java_kt-kt.jar com.snap.valdi.network.DefaultHTTPRequestManager | grep -i "DefaultHTTPRequestManager(\|DefaultConstructorMarker"

and get the following:

  public com.snap.valdi.network.DefaultHTTPRequestManager(android.content.Context, kotlin.jvm.functions.Function1<? super java.net.URL, ? extends java.net.URLConnection>);
  public com.snap.valdi.network.DefaultHTTPRequestManager(android.content.Context);

private val threadCount = AtomicInteger(0)

private val executors: ExecutorService = ThreadPoolExecutor(
MAX_CONCURRENT_REQUESTS,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Seems like this limits Android to four requests globally, whereas URLSession’s connection limit is per host.

I double-checked with four stalled requests to 127.0.0.1 followed by an immediately responding request using the distinct hostname localhost. The latter remained queued because all four executor workers were occupied. This means one slow host can still block unrelated hosts, unlike the HTTPMaximumConnectionsPerHost behavior referenced in the PR description.

Is the global cap intentional? If so, could we clarify the parity claim? Otherwise, would it make sense to preserve capacity across hosts?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

yeah that's my mis-read, sorry.

the connections per host difference means this is an android improvement, but not ios parity.
I looked into a few options to get something closer to ios logic. Looks like the best way if having them more closely aligned would be to use https://github.com/lysine-dev/okhttp as the default (but that's a discussion way beyond the scope of this fix - side note, I did check to see if I could set okhttp as the default in my app without any Valdi changes required via ValdiRuntimeManager.addRequestManager, but ran into a bug there - I'll raise a fix for that separately).

So, I've made a small update with tests to support the same maximum requests per hosts behaviour, with tests to the cross paltform behaviour aligns better.

@tx3stn
tx3stn force-pushed the fix-android-cancelation branch from 0bfcae7 to 226f37f Compare August 15, 2026 11:38
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