-
Notifications
You must be signed in to change notification settings - Fork 122
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
Breaking: Asynchronous message sending with async
(iOS 13 required)
#527
Conversation
…ng iOS 13 as minimum version
0a666cf
to
547318c
Compare
A few higher-level comments to complement forthcoming in-file ones:
let handle = Task {
return await add(newPhoto, toGalleryNamed: "Spring Adventures")
}
if foo { handle.cancel } //my add
let result = await handle.value I don't think the current |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See summary in immediately preceding comment.
Example/Source/View Controllers/Network/Configuration/ModelViewController.swift
Show resolved
Hide resolved
@@ -182,25 +182,29 @@ internal class NetworkManager { | |||
/// - initialTtl: The initial TTL (Time To Live) value of the message. | |||
/// If `nil`, the default Node TTL will be used. | |||
/// - applicationKey: The Application Key to sign the message. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should document under what conditions the method throws
|
||
private extension NetworkManager { | ||
|
||
func setDeliveryCallback(for destination: Address, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These apis assume there is a single callback per destination. May need similar approach as in NetworkConnection
where there is a stack of delegates.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The limit per single callback per destination comes from the mesh spec. A Node can send only one message to a single destination address at a time.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wasn't clear in my comment. There can be >1 listener in App (UI plus e.g. the executeNow() function in ConfigurationViewController if permitted to continue executing after user navigates away, or some logging function) When add SwiftUI it is way easier if UIkit ViewControllers continue to pass delegate to each other and add a 2nd (model controller) listener for when a SwiftUI view is responsible for presenting errors to end-user.
} | ||
} | ||
|
||
func setResponseCallback(for request: AcknowledgedMeshMessage, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Difficult to predict/derive the response source address(es) when sending to a Group address. Only approach we have found to work is matching opcodes and all the msg parameters.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When an acknowledged message is sent to a Group address the callback will not await for responses (as 0+ of such is equally expected), but rather will be called upon delivery (that is when the request is sent).
The response callback is only send for acknowledged messages sent to a Unicast Address.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok - I didn't realize that is what these functions were for. Thought it was some new capability for App to listen in to what is happening...
@@ -276,8 +265,8 @@ public extension MeshNetworkManager { | |||
guard let networkManager = networkManager else { | |||
return | |||
} | |||
queue.async { | |||
networkManager.handle(incomingPdu: data, ofType: type) | |||
Task { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also provide an async version of bearerDidDeliverData()
so that can evolve NetworkConnection
?
Task.detached {}
since typically switch actor?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm actually considering removing async
from handle(incomingPdu:ofType:)
in NetworkManager
. It's not asynchronous anyway.
I'd just call it from Task.detached
in MeshNetworkManager
, but the bearerDidDeliverData
can be non-blocking only.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agree - ultimately want a synchronous call from NetworkConnection/BluetoothManager thread to quickly deposit incoming msg in a receive buffer so that thread/task waiting doesn't cause msg reordering. Unclear how to best architect this until start introducing actors (wouldn't want to block while waiting for a networking stack actor to complete something). Since AccessLayer is out of scope of this chg, didn't make the comment yesterday.
@@ -310,7 +299,7 @@ public extension MeshNetworkManager { | |||
let _ = meshNetwork?.applicationKeys[publish.index] else { | |||
return nil | |||
} | |||
queue.async { | |||
Task { | |||
networkManager.publish(message, from: model) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
May need a revised .publish api. I don't think can capture and pass in Model class to Task. May depend on XCode version what warnings are generated (the purple ones...) and which Swift version it becomes an error.
I may have this mixed up with requirements for Actors.....
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll leave it for now like that. I have no warnings.
If we see issues later, this can be refactored.
func send(_ message: MeshMessage, | ||
from localElement: Element? = nil, to destination: MeshAddress, | ||
withTtl initialTtl: UInt8? = nil, | ||
using applicationKey: ApplicationKey, | ||
completion: ((Result<Void, Error>) -> ())? = nil) throws -> MessageHandle { | ||
using applicationKey: ApplicationKey) async throws { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
may need api of form
func send(message: MeshMessage, localElementIdx: UInt8?=nil, destination: MeshAddress, initialTtl:, applicationKeyIdx: KeyIndex) async throws {
such that synchronous code that makes the call from within a new Task where the task captures required Sendable method parameters.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same as above.
func send(_ message: UnacknowledgedConfigMessage, to destination: Address, | ||
withTtl initialTtl: UInt8? = nil, | ||
completion: ((Result<Void, Error>) -> ())? = nil) throws -> MessageHandle { | ||
withTtl initialTtl: UInt8? = nil) async throws { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As above, may need revised api with Sendable paramteters.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Like above, perhaps later. So far it works for me without warnings.
@@ -819,8 +763,8 @@ public extension MeshNetworkManager { | |||
print("Error: Mesh Network not created") | |||
throw MeshNetworkError.noNetwork | |||
} | |||
queue.async { | |||
networkManager.send(message) | |||
Task { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Provide an async variant of the method?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Later perhaps.
async
-first approachasync
(iOS 13 required)
This PR is the second attempt after #526 to implement
async
methods for sending messages to the mesh network.This time message types were left alone, so the
send(...)
methods return genericMeshResponse
orConfigResponse
types and user has to cast manually.Apart from that:
async
/await
.Task
instead ofDispatchQueue
. Thequeue
param in theMeshNetworkManager.init
is now deprecated and unused.send(..)
methods use the async methods under the hood.Task.cancel()
, but the callback-based methods still returnMessageHandle
as they used to.I did not migrate
NetworkManager
to be an actor. That will have to wait.