Skip to content

[in_app_purchase] Write to the transactions update queue from the main thread #9068

New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Merged
merged 3 commits into from
Apr 21, 2025

Conversation

edpizzi
Copy link
Contributor

@edpizzi edpizzi commented Apr 14, 2025

Equeue to the channel from the main thread to avoid channel safety issues.

Background: sendTransactionUpdate can be called from the main thread, where it is safe to send messages on the onTransactionsUpdated channel. This seems to happen when the purchase code path succeeds and generates a transaction. However sendTransactionUpdate can also be called on non-main threads serving background executors, when transaction callbacks result from restoring purchases or other transactions are reported to the transaction subscription (eg. a purchase made outside of the app, or a refund is issued -- this can be triggered from XCode's StoreKit Test transaction log). In these cases, at least sometimes, an error is logged about writing to a channel on a non-platform thread (see the issue below).

Enqueue on the main thread to avoid channel thread-safety issues. This could be optimized further, but I believe this is safe.

Resolves flutter/flutter#166493

Pre-Review Checklist

Footnotes

  1. Regular contributors who have demonstrated familiarity with the repository guidelines only need to comment if the PR is not auto-exempted by repo tooling. 2 3

@edpizzi edpizzi requested a review from LouiseHsu as a code owner April 14, 2025 15:21
@flutter-dashboard
Copy link

It looks like this pull request may not have tests. Please make sure to add tests or get an explicit test exemption before merging.

If you are not sure if you need tests, consider this rule of thumb: the purpose of a test is to make sure someone doesn't accidentally revert the fix. Ask yourself, is there anything in your PR that you feel it is important we not accidentally revert back to how it was before your fix?

Reviewers: Read the Tree Hygiene page and make sure this patch meets those guidelines before LGTMing.If you believe this PR qualifies for a test exemption, contact "@test-exemption-reviewer" in the #hackers channel in Discord (don't just cc them here, they won't see it!). The test exemption team is a small volunteer group, so all reviewers should feel empowered to ask for tests, without delegating that responsibility entirely to the test exemption group.

@edpizzi
Copy link
Contributor Author

edpizzi commented Apr 14, 2025

Hi @LouiseHsu -- I'm not able to figure out how to test this on my own. With a small change to testRestoreProductSuccess (waiting for purchaseExpectation before triggering restorePurchases), I can demonstrate a callback that comes in on a non-main thread, the error condition here. However in the unit test transactionCallbackAPI is null since we use a constructor for the plugin that does not set up a channel, so the error condition does not occur -- we don't send a message to a channel from a non-main thread, since we don't send a message at all and there is no channel.

However the reason the error is avoided is synthetic, and is specific to the unit test setup. I think testing this would require changing how the tests are structured, which I'm not sure I can take on, and either way would require maintainer guidance (i.e., why are the tests set up this way, and what is the cost / complexity of changing it to include a message channel). Even then, the success criterion would be "log message does not occur", which wouldn't cause the test to fail I don't think (unless there's a debug assert in there; I haven't seen one), so we'd have to look at the test output, unless there's a log sink we can inspect.

In any case, I think the fix is quite important, while figuring out a test strategy is less important, and I don't think it's wise to gate a fix on (unless it's disputed whether this is a crash risk). But that's just my opinion. Either way, if we gate on a test strategy, I'll require some help, and it might be better handed off to someone with domain expertise.

(There's some relevant discussion in #hackers-new in the discord as I've been trying to figure this out, but the important bits are contained here.)

@edpizzi edpizzi force-pushed the iap-storekit2-channel-thread branch from 8ecaa2c to 40e7742 Compare April 14, 2025 20:55
@edpizzi
Copy link
Contributor Author

edpizzi commented Apr 14, 2025

Ok, I implemented a testing strategy where we create a fake InAppPurchase2CallbackAPIProtocol that just asserts that it's called on the main thread, and set it on the plugin in InAppPurchase2PluginTests. Tests fail before the fix, and pass after.

@edpizzi edpizzi force-pushed the iap-storekit2-channel-thread branch 2 times, most recently from cad0e4f to 3fb7a77 Compare April 15, 2025 16:25
@LouiseHsu
Copy link
Contributor

Looks good to me! I think the failed test is flaky, Im just going to rerun it, and if it passes ill approve this :)

Copy link
Contributor

@LouiseHsu LouiseHsu left a comment

Choose a reason for hiding this comment

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

LGTM! Thanks for fixing this <3

@LouiseHsu LouiseHsu added the autosubmit Merge PR when tree becomes green via auto submit App label Apr 15, 2025
@auto-submit auto-submit bot removed the autosubmit Merge PR when tree becomes green via auto submit App label Apr 15, 2025
Copy link
Contributor

auto-submit bot commented Apr 15, 2025

autosubmit label was removed for flutter/packages/9068, because This PR has not met approval requirements for merging. The PR author is not a member of flutter-hackers and needs 1 more review(s) in order to merge this PR.

  • Merge guidelines: A PR needs at least one approved review if the author is already part of flutter-hackers or two member reviews if the author is not a flutter-hacker before re-applying the autosubmit label. Reviewers: If you left a comment approving, please use the "approve" review action instead.

@LouiseHsu
Copy link
Contributor

oh oops. I forgot about the 2 reviewer rule. Tagging @hellohuanlin for secondary review

@LouiseHsu LouiseHsu requested a review from hellohuanlin April 15, 2025 19:33
@edpizzi
Copy link
Contributor Author

edpizzi commented Apr 17, 2025

Friendly ping, @hellohuanlin, please and thank you.

@LouiseHsu - what's the release schedule for in_app_purchase_storekit? Developers probably shouldn't enable StoreKit2 until this is released. Is there a target date for enabling StoreKit2 by default?

@LouiseHsu
Copy link
Contributor

Friendly ping, @hellohuanlin, please and thank you.

@LouiseHsu - what's the release schedule for in_app_purchase_storekit? Developers probably shouldn't enable StoreKit2 until this is released. Is there a target date for enabling StoreKit2 by default?

I'm aiming to enable Storekit2 sometime before the end of the month, but there's no strict schedule. There's a couple other contributer PRs I would like to land first as well.

@edpizzi edpizzi force-pushed the iap-storekit2-channel-thread branch from 3fb7a77 to f3ae538 Compare April 17, 2025 23:10
@edpizzi
Copy link
Contributor Author

edpizzi commented Apr 17, 2025

Resolving a version number merge conflict. I assume I keep this as 0.3.22+1 unless there will be a release of some kind (vs. 0.3.23)?

case .success: break
case .failure(let error):
print("Failed to send transaction updates: \(error)")
DispatchQueue.main.async {
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: can you do

Task { @MainActor in 
  ....
}

To be consistent with other parts of the code?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done. I'm afraid I see tests failing at main (testGetTransactionJsonRepresentation, testFinishTransaction, at bfb42a6). I assume it'll get sorted out.

@edpizzi edpizzi force-pushed the iap-storekit2-channel-thread branch 2 times, most recently from 6fa17df to 0ed6633 Compare April 19, 2025 05:50
@edpizzi edpizzi force-pushed the iap-storekit2-channel-thread branch from 0ed6633 to 19fe744 Compare April 19, 2025 19:49
@hellohuanlin hellohuanlin added the autosubmit Merge PR when tree becomes green via auto submit App label Apr 21, 2025
@auto-submit auto-submit bot merged commit 138b323 into flutter:main Apr 21, 2025
82 checks passed
engine-flutter-autoroll added a commit to engine-flutter-autoroll/flutter that referenced this pull request Apr 22, 2025
github-merge-queue bot pushed a commit to flutter/flutter that referenced this pull request Apr 22, 2025
flutter/packages@ac21f53...f1fc965

2025-04-22 sys.int64@gmail.com [vector_graphics_compiler] fix: Stroke
opacity not applied (flutter/packages#8986)
2025-04-21 edpizzi@gmail.com [in_app_purchase] Write to the transactions
update queue from the main thread (flutter/packages#9068)
2025-04-21 32538273+ValentinVignal@users.noreply.github.com
[go_router_builder] Add `caseSensitive` to `TypedGoRoute`
(flutter/packages#9096)

If this roll has caused a breakage, revert this CL and stop the roller
using the controls here:
https://autoroll.skia.org/r/flutter-packages-flutter-autoroll
Please CC flutter-ecosystem@google.com on the revert to ensure that a
human
is aware of the problem.

To file a bug in Flutter:
https://github.com/flutter/flutter/issues/new/choose

To report a problem with the AutoRoller itself, please file a bug:
https://issues.skia.org/issues/new?component=1389291&template=1850622

Documentation for the AutoRoller is here:
https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md
raju8000 pushed a commit to raju8000/flutter_packages that referenced this pull request Apr 26, 2025
…n thread (flutter#9068)

Equeue to the channel from the main thread to avoid channel safety issues.

Background: `sendTransactionUpdate` can be called from the main thread, where it is safe to send messages on the `onTransactionsUpdated` channel. This seems to happen when the purchase code path succeeds and generates a transaction. However `sendTransactionUpdate` can also be called on non-main threads serving background executors, when transaction callbacks result from restoring purchases or other transactions are reported to the transaction subscription (eg. a purchase made outside of the app, or a refund is issued -- this can be triggered from XCode's StoreKit Test transaction log). In these cases, at least sometimes, an error is logged about writing to a channel on a non-platform thread (see the issue below).

Enqueue on the main thread to avoid channel thread-safety issues. This could be optimized further, but I believe this is safe.

Resolves flutter/flutter#166493

## Pre-Review Checklist

[^1]: Regular contributors who have demonstrated familiarity with the repository guidelines only need to comment if the PR is not auto-exempted by repo tooling.
# for free to join this conversation on GitHub. Already have an account? # to comment
Labels
autosubmit Merge PR when tree becomes green via auto submit App p: in_app_purchase platform-ios platform-macos
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[in_app_purchase] StoreKit2 error logs about using a platform channel from a native thread
3 participants