-
Notifications
You must be signed in to change notification settings - Fork 495
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
Add async/await support for job based messages #170
Conversation
….0 tests to run. See: xunit/xunit#158
I don't like this bit... it doesn't make sense to me for a job to be a subclass of the job id from a pure design perspective, it seems like the only justification for this is backwards compatibility. Compilation compatibility could also be achieved through an implicit cast operator, but that would fall over for consumers that use Otherwise, I'd rather make a breaking change and bump the major version (finally?). |
We still need to keep JobIDs around for matching callbacks to requests into the far foreseeable future, even for a major version bump I wouldn't want to make a breaking change like that. Some sort of implicit cast sounds compelling, though. |
After thinking about it a little, I don't think any implicit casts will work here either. Consider: AppOwnershipTicketCallback callback = await steamApps.GetAppOwnershipTicket( 440 ); I don't think the compiler will be able to work out that |
I was thinking the other way around.
|
An implicit cast is provided for compatibility with existing code.
Okay, with this we now have some initial handling for heartbeats and remote failures (from #171). I've also switched it over to an implicit cast, which I think retains the same amount of source code compatibility as before. All that's really left is to implement the logic and tests for remote failure exceptions. |
This branch has been running in production at @SteamDatabase for a couple of days now, and makes use of |
Worth considering, at the risk of breaking backwards compatibility? |
Far too much breakage in my opinion. Edit: Here's a choice quote from Stephen Toub on the convention:
In our case, our entire design is already around asynchronous method completion, so I'd agree with the overkill sentiment. |
Not directly related to this PR, but perhaps look into making |
What is the reason for the If it's due to backwards compatibility then I'd either recommend splitting this feature into a dedicated NuGet package or following SemVer's guidelines and release a new major version that explicitly allows breaking backwards compatibility changes. |
Backwards compatibility is indeed the reasoning. We don't have any plans to make any significant breaking changes (such was removing our entire callback scheme for job based messages) any time in the future. Having a separate library version that follows the TPL conventions more closely is an interesting idea, but I wouldn't be happy with the maintenance burden. |
Current coverage is
|
{ | ||
jobManager.HeartbeatJob( packetMsg.TargetJobID ); | ||
} | ||
void HandleJobFailed( IPacketMsg packetMsg ) |
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.
It might be a good idea to turn this into a callback too, so that users not using async
can see catch their failed jobs.
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.
There's #171 regarding this, but I'm not sure if consumers really want to go through the code gymnastics of handling failures (and likely heartbeats) by having multiple callbacks.
So here's an experiment that might be worth mainlining.
The idea here is that we introduce an
AsyncJob
object that inherits fromJobID
, and have all our job related client message handler functions return this object. Client code will continue to compile since existing code can work with theJobID
superclass just fine.The
AsyncJob
objects expose aGetAwaiter()
to support awaiting directly on that object, and aToTask()
to get the underlying TPL task that the job itself is wrapping.Awaiting the result of the job will directly return the callback object for that job, i.e.: awaiting a call to
GetAppOwnershipTicket
will return aAppOwnershipTicketCallback
without any callback subscription boilerplate.The
AsyncJob
instances are created by client message handlers, and get tied to the handlers' parentSteamClient
instance.SteamClient
then handles the job's completion throughPostCallback
: when a callback for thatJobID
is posted,SteamClient
completes the job and await returns the callback.SteamClient
is also tasked with timing out any jobs that never receive a response from Steam. The default timeout is 1 minute, but can be managed with theAsyncJob.Timeout
property. Any jobs that don't receive a response are canceled and can be handled through regular exception handling.There's also an additional
AsyncJobMultiple
for the instances where Steam can reply with multiple messages to a single job based request. In this case, awaiting on this job will instead return aResultSet
object that contains a list of the retrieved callbacks. There's also a few cases involved with this one:ResultSet
is returned with all the messages andComplete
will indicate the result set is complete.ResultSet
will be returned with what the client managed to receive andComplete
will be set to false.Caveats: