[RFC PATCH v4 2/5] server: Add a new server request "notify_async_direct_result."

Jinoh Kang jinoh.kang.kr at gmail.com
Thu Feb 3 10:23:21 CST 2022


On 2/3/22 08:45, Zebediah Figura wrote:
> On 1/29/22 01:47, Jinoh Kang wrote:

...

>> diff --git a/server/async.c b/server/async.c
>> index e169bb23225..8852ff1c6d5 100644
>> --- a/server/async.c
>> +++ b/server/async.c
>> @@ -752,3 +752,65 @@ DECL_HANDLER(get_async_result)
>>       }
>>       set_error( iosb->status );
>>   }
>> +
>> +/* Notify direct completion of async and close the wait handle if not blocking */
>> +DECL_HANDLER(notify_async_direct_result)
>> +{
>> +    struct async *async = (struct async *)get_handle_obj( current->process, req->handle, 0, &async_ops );
>> +
>> +    if (!async) return;
>> +
>> +    /* we only return an async handle for asyncs created via create_request_async() */
>> +    assert( async->iosb );
>> +
>> +    if (async->unknown_status && !async->pending &&
>> +        async->terminated && async->alerted &&
>> +        async->iosb->status == STATUS_PENDING)
> 
> So, these are all things that happen to be true assuming the client is behaving correctly,

We still need to assert "async->terminated && async->alerted" so that "async_set_result()" works correctly on STATUS_PENDING case.
The last one seems actually unnecessary though (looks like a leftover from v3 patch, my apologies).

> but the way this is written doesn't exactly make that clear (in particular, with a large if block, I can't see what the "else" case looks like immediately). I would recommend inverting the condition,

Sounds good.

> returning early, and possibly returning some error condition (STATUS_INVALID_PARAMETER?) to make it clear that the client is misbehaving.

In that case, I'd like to hear some suggestions on how the client would handle (or not handle) the "STATUS_INVALID_PARAMETER" error.
>     v3 -> v4:
>     - make robust against abrupt transition/cancellation:
>       don't return STATUS_ACCESS_DENIED on precondition failure
>       (client won't know what to do with STATUS_ACCESS_DENIED anyway) 

> 
> Note also that "async->pending" is kind of irrelevant here, it semantically is only meaningful when considering things like whether the request should queue completion. I.e. it can kind of be considered undefined if async->unknown_status == 1.

"async->unknown_status" should imply "!async->pending" anyway, so I see what you're saying here.

> 
> I'm not sure async->iosb->status is meaningful either, is it? We check for "iosb->status == STATUS_PENDING" in a few places, but I'm not really sure that's a good idea; we should probably be treating it more like "the value that gets returned in the userspace IOSB".

I concur.

> In any case I don't think it has semantic value here.
> 
>> +    {
>> +        /* async->terminated is 1, so async_terminate() won't do anything.
>> +         * We need to set the status and result for ourselves.
>> +         */
>> +        async->iosb->status = req->status;
>> +        async->iosb->result = req->information;
> 
> Except that neither of these variables actually matter past async_set_result(), right? We shouldn't need to bother.

async_handoff() is one of those few places where 'we check for "iosb->status == STATUS_PENDING"'.
Since the "async_terminate()" call inside "async_handoff()" doesn't work here (async is in terminated state), the async->iosb->status value set here will be used to determine the eventual pending status of async. If STATUS_PENDING, some necessary flags (async->direct_result and async->pending) will also be set/cleared.
Note that the set_error() below is still required so that initial_status is properly set and synchronous failure case properly done, etc.

> 
>> +
>> +        /* Set status for async_handoff(). */
>> +        set_error( async->iosb->status );
>> +
>> +        /* The async_handoff() call prior to the current server request was
>> +         * effectively a no-op since async->unknown_status is 1.  Calling it
>> +         * again with async->unknown_status = 0 will do the remaining steps.
>> +         *
>> +         * NOTE: async_handoff() is being called with async->terminated = 1.
>> +         */
>> +        async->unknown_status = 0;
>> +        async_handoff( async, NULL, 0 );
> 
> I kind of get the idea here, but my intuition tells me that this is a bad idea. I have to think too hard about what's involved in async_handoff().

You're right--I just had to explain that above and it already sounds too complicated.  Perhaps only open code the relevant parts?

> 
> Do we even get anything out of this that's not covered by async_set_initial_status(), plus async_set_result() and the wait_handle management below?

Yes.  Especially this part:
>    if (async->iosb->status != STATUS_PENDING)
>    {
>        if (result) *result = async->iosb->result;
>        async->signaled = 1;
>    }
>    else
>    {
>        async->direct_result = 0;
>        async->pending = 1;
>        if (!async->blocking)
>        {
>            close_handle( async->thread->process, async->wait_handle);
>            async->wait_handle = 0;
>        }
>    }

The flags need to be properly set (especially in the else branch) so that the subsequent wait_async() would properly function.
Also note the blocking part, synchronous vs. asynchronous failure, etc.

> 
>> +
>> +        /* If the I/O has completed successfully, the client would have already
>> +         * set the IOSB. Therefore, we can skip waiting on wait_handle and do
>> +         * async_set_result() directly.
>> +         */
>> +        async_set_result( &async->obj, async->iosb->status, async->iosb->result );
>> +
>> +        /* Close wait handle here to avoid extra server round trip if the I/O
>> +         * has completed successfully.
>> +         *
>> +         * - If STATUS_PENDING, async_handoff() decides whether to close
>> +         *   the wait handle for us.  The wait handle is kept open if
>> +         *   async->blocking is set (i.e. we're going to block on it).
>> +         *
>> +         * - If failed (NT_ERROR), async_handoff() always closes
>> +         *   the wait_handle, which means async->wait_handle == 0.
>> +         */
>> +        if (async->wait_handle && async->iosb->status != STATUS_PENDING)
> 
> Isn't the first part of this condition redundant?

It isn't.

> If the client gave us a handle in the first place then async->wait_handle must be nonzero.

It can be set to zero in the middle by async_handoff()  (see above).

> 
>> +        {
>> +            close_handle( async->thread->process, async->wait_handle );
>> +            async->wait_handle = 0;
>> +        }
>> +    } /* Otherwise, async have been abruptly transitioned (cancelled?). */
>> +
>> +    /* async_set_result() may have overriden the error value.  Reset it. */
>> +    set_error( async->iosb->status );
> 
> Does the client even need to care what this request returns?

I don't think it normally should, but I don't exactly feel easy about silencing error from the server, either.

> 
>> +    reply->handle = async->wait_handle;
>> +
>> +    release_object( &async->obj );
>> +}
>> diff --git a/server/protocol.def b/server/protocol.def
>> index 02e73047f9b..d79eca074a0 100644
>> --- a/server/protocol.def
>> +++ b/server/protocol.def
>> @@ -2163,6 +2163,16 @@ enum message_type
>>   @END
>>     +/* Notify direct completion of async and close the wait handle if not blocking */
>> + at REQ(notify_async_direct_result)
> 
> I might recommend calling this "set" instead of "notify". Consider e.g. "set_irp_result".
> 
>> +    obj_handle_t   handle;        /* wait handle */
>> +    unsigned int   status;        /* completion status */
>> +    apc_param_t    information;   /* IO_STATUS_BLOCK Information */
>> + at REPLY
>> +    obj_handle_t   handle;        /* wait handle, or NULL if closed */
> 
> Why do we need this?

We could have just returned a boolean to indicate whether the wait handle has been closed by us.
(Note: the wait handle is closed when we're transitioning to STATUS_PENDING, _and_ async->blocking == 0.)
However, I found that just returning either the original handle or NULL to indicate that simplifies code a lot.

> 
>> + at END
>> +
>> +
>>   /* Perform a read on a file object */
>>   @REQ(read)
>>       async_data_t   async;         /* async I/O parameters */


-- 
Sincerely,
Jinoh Kang



More information about the wine-devel mailing list