Bug 183089 - [Curl] Bug fix on suspend/resume behavior.
Summary: [Curl] Bug fix on suspend/resume behavior.
Status: RESOLVED FIXED
Alias: None
Product: WebKit
Classification: Unclassified
Component: Platform (show other bugs)
Version: WebKit Nightly Build
Hardware: Unspecified Unspecified
: P2 Normal
Assignee: Basuke Suzuki
URL:
Keywords: InRadar
Depends on:
Blocks:
 
Reported: 2018-02-23 11:41 PST by Basuke Suzuki
Modified: 2018-05-18 11:31 PDT (History)
10 users (show)

See Also:


Attachments
patch (1.29 KB, patch)
2018-02-23 12:03 PST, Basuke Suzuki
no flags Details | Formatted Diff | Diff
FIX (1.50 KB, patch)
2018-05-08 15:08 PDT, Basuke Suzuki
no flags Details | Formatted Diff | Diff
PATCH (11.40 KB, patch)
2018-05-16 15:13 PDT, Basuke Suzuki
no flags Details | Formatted Diff | Diff
FIX spacing (11.33 KB, patch)
2018-05-16 15:22 PDT, Basuke Suzuki
no flags Details | Formatted Diff | Diff
Add descriptive comment (12.50 KB, patch)
2018-05-17 12:11 PDT, Basuke Suzuki
no flags Details | Formatted Diff | Diff
PATCH (12.72 KB, patch)
2018-05-17 12:30 PDT, Basuke Suzuki
no flags Details | Formatted Diff | Diff
Archive of layout-test-results from ews202 for win-future (12.86 MB, application/zip)
2018-05-17 19:42 PDT, EWS Watchlist
no flags Details
Fixed (14.06 KB, patch)
2018-05-17 23:45 PDT, Basuke Suzuki
youennf: review+
ews-watchlist: commit-queue-
Details | Formatted Diff | Diff
Archive of layout-test-results from ews204 for win-future (12.76 MB, application/zip)
2018-05-18 06:44 PDT, EWS Watchlist
no flags Details
PATCH (14.09 KB, patch)
2018-05-18 10:48 PDT, Basuke Suzuki
no flags Details | Formatted Diff | Diff

Note You need to log in before you can comment on or make changes to this bug.
Description Basuke Suzuki 2018-02-23 11:41:57 PST
1. Asser error occured when suspend called in CurlRequest::setupTransfer().
   Because CurlRequest::setupTransfer() is called on worker thread.

2. CurlRequest that construct with enabled defferLoading flag doesn't resume
   when resume called before CurlRequest::setupTransfer().
   Because m_shouldSuspend flag doesn't change after construct.
Comment 1 Basuke Suzuki 2018-02-23 12:03:20 PST
Created attachment 334541 [details]
patch

Bug fix
Comment 2 Don Olmstead 2018-02-23 12:09:08 PST
Comment on attachment 334541 [details]
patch

View in context: https://bugs.webkit.org/attachment.cgi?id=334541&action=review

> Source/WebCore/platform/network/curl/CurlRequest.cpp:141
> +    m_shouldSuspend = false;
>      setRequestPaused(false);

Looks fine just curious if the m_shouldSuspend should live in setRequestPaused
Comment 3 Alex Christensen 2018-03-15 13:30:31 PDT
Comment on attachment 334541 [details]
patch

View in context: https://bugs.webkit.org/attachment.cgi?id=334541&action=review

> Source/WebCore/platform/network/curl/CurlRequest.cpp:-132
> -    ASSERT(isMainThread());

Could this be changed to ASSERT(!isMainThread()); ?
It seems like suspend and resume should be called on the main thread.
Comment 4 Basuke Suzuki 2018-03-15 15:44:28 PDT
Comment on attachment 334541 [details]
patch

View in context: https://bugs.webkit.org/attachment.cgi?id=334541&action=review

Thank you for reviewing.

>> Source/WebCore/platform/network/curl/CurlRequest.cpp:-132
>> -    ASSERT(isMainThread());
> 
> Could this be changed to ASSERT(!isMainThread()); ?
> It seems like suspend and resume should be called on the main thread.

suspend() will be called from background network thread when the request is anync. It will be called when the request is sync.

>> Source/WebCore/platform/network/curl/CurlRequest.cpp:141
>>      setRequestPaused(false);
> 
> Looks fine just curious if the m_shouldSuspend should live in setRequestPaused

There's a situation setRequestPaused is called without changing the flag.
Comment 5 Alex Christensen 2018-03-20 14:46:23 PDT
Comment on attachment 334541 [details]
patch

View in context: https://bugs.webkit.org/attachment.cgi?id=334541&action=review

>>> Source/WebCore/platform/network/curl/CurlRequest.cpp:-132
>>> -    ASSERT(isMainThread());
>> 
>> Could this be changed to ASSERT(!isMainThread()); ?
>> It seems like suspend and resume should be called on the main thread.
> 
> suspend() will be called from background network thread when the request is anync. It will be called when the request is sync.

Suspend and resume ought to mirror each other.  I think something is wrong if they don't.
Comment 6 Basuke Suzuki 2018-05-08 15:08:16 PDT
Created attachment 339887 [details]
FIX

Keep existing suspend/resume pair as is. Change to call internal state change method. Also fix updating the flag for initial suspension.
Comment 7 youenn fablet 2018-05-10 10:41:59 PDT
Comment on attachment 339887 [details]
FIX

View in context: https://bugs.webkit.org/attachment.cgi?id=339887&action=review

> Source/WebCore/platform/network/curl/CurlRequest.cpp:226
> +        setRequestPaused(true);

I guess this change is due to the call being sometimes not done in the main thread.
Is it right?
If so, I wonder whether this code is thread-safe?
Can there be case where setRequestPaused(true) and setRequestPaused(false) are called in the same time in a different thread?
Maybe setupTransfer should always be called in the main thread?


I also wonder whether we can have some tests related to those changes.
Comment 8 Basuke Suzuki 2018-05-16 15:13:26 PDT
Created attachment 340529 [details]
PATCH

Added mutex to protect the status change conflict.
Comment 9 Basuke Suzuki 2018-05-16 15:22:53 PDT
Created attachment 340530 [details]
FIX spacing
Comment 10 youenn fablet 2018-05-17 08:56:52 PDT
Comment on attachment 340530 [details]
FIX spacing

View in context: https://bugs.webkit.org/attachment.cgi?id=340530&action=review

> Source/WebCore/platform/network/curl/CurlRequest.cpp:116
> +            runOnWorkerThreadIfRequired([this, protectedThis = makeRef(*this)]() {

By doing so, there is a chance that CurlRequest will be destroyed in the worker thread.
Is it safe to do so?

> Source/WebCore/platform/network/curl/CurlRequest.cpp:154
> +void CurlRequest::runOnMainThread(WTF::Function<void()>&& task)

It does not need to be a method, a static function would be good enough.
Comment 11 Basuke Suzuki 2018-05-17 12:11:19 PDT
Created attachment 340621 [details]
Add descriptive comment
Comment 12 Basuke Suzuki 2018-05-17 12:16:29 PDT
Oh, I didn't notice about your review. I'm fixing that.
Comment 13 Basuke Suzuki 2018-05-17 12:28:07 PDT
(In reply to youenn fablet from comment #10)
> Comment on attachment 340530 [details]
> FIX spacing
> 
> View in context:
> https://bugs.webkit.org/attachment.cgi?id=340530&action=review
> 
> > Source/WebCore/platform/network/curl/CurlRequest.cpp:116
> > +            runOnWorkerThreadIfRequired([this, protectedThis = makeRef(*this)]() {
> 
> By doing so, there is a chance that CurlRequest will be destroyed in the
> worker thread.
> Is it safe to do so?

Do you mean from the resource management perspective? If so, that's what we want them to do. Things are prepared in setupTransfer() method except m_request and m_formDataStream which is isolated form others. The method runs in worker thread. It make sense they destroyed in background.
Comment 14 Basuke Suzuki 2018-05-17 12:30:26 PDT
Created attachment 340625 [details]
PATCH

Make the method to static function and fix a typo.
Comment 15 youenn fablet 2018-05-17 18:51:14 PDT
Comment on attachment 340625 [details]
PATCH

View in context: https://bugs.webkit.org/attachment.cgi?id=340625&action=review

> Source/WebCore/platform/network/curl/CurlRequest.cpp:41
> +static void runOnMainThread(WTF::Function<void()>&& task);

No need for WTF::

> Source/WebCore/platform/network/curl/CurlRequest.cpp:148
>      setRequestPaused(false);

Maybe these if(isCompletedOrCancelled()) should be done inside setRequestPaused

> Source/WebCore/platform/network/curl/CurlRequest.cpp:157
>              task(*this, *client);

Would the following work:
task(*this, makeRef(*client))

> Source/WebCore/platform/network/curl/CurlRequest.cpp:376
> +            m_handlePaused = true;

I understand that returning CURL_WRITEFUNC_PAUSE will make curl handle be paused, right?
If so, that is why m_handlePaused is set to true, right?
setCallbackPaused(true) is the only needed to update m_isPausedCallback?

That seems okayish but a bit difficult to understand.

> Source/WebCore/platform/network/curl/CurlRequest.cpp:603
>              didCompleteTransfer(m_finishedResultCode);

If you have a lot this pattern:
if (!m_isSyncRequest)
   runOnWorkerThread(WTFMove(a))
else
  a();
Maybe it could be made as a method.

> Source/WebCore/platform/network/curl/CurlRequest.cpp:610
> +        LockHolder lock(m_pauseStateMutex);

It seems that isPaused is only used in worker thread.
If m_shouldSuspend and m_isPausedOfRequest are only useful in worker thread, maybe it is just better to use runOnWorkerThreadIfRequired inside setRequestPaused/setCallbackPaused to do the actual state updates.

> Source/WebCore/platform/network/curl/CurlRequest.cpp:644
> +    */

We usually use // instead of /* */

> Source/WebCore/platform/network/curl/CurlRequest.cpp:672
> +            invokeCancel();

Maybe you can just write:
runOnMainThread([protectedThis = WTFMove(protectedThis)]() {
     protectedThis->cancel();
});

You would need to make the embedding lambda mutable

> Source/WebCore/platform/network/curl/CurlRequest.h:109
> +    void runOnWorkerThreadIfRequired(WTF::Function<void()>&&);

No longer need for WTF::

> Source/WebCore/platform/network/curl/CurlRequest.h:185
> +    bool m_handlePaused { false };

I would rename it to m_isHandlePaused.
Since you want to make sure it is only accessed in worker thread, I would tend to add r/w accessors with debug assertions in it.
That could make the model clearer.
Comment 16 EWS Watchlist 2018-05-17 19:42:23 PDT
Comment on attachment 340625 [details]
PATCH

Attachment 340625 [details] did not pass win-ews (win):
Output: http://webkit-queues.webkit.org/results/7717873

New failing tests:
http/tests/security/contentSecurityPolicy/userAgentShadowDOM/allow-audio.html
Comment 17 EWS Watchlist 2018-05-17 19:42:36 PDT
Created attachment 340674 [details]
Archive of layout-test-results from ews202 for win-future

The attached test failures were seen while running run-webkit-tests on the win-ews.
Bot: ews202  Port: win-future  Platform: CYGWIN_NT-6.1-2.9.0-0.318-5-3-x86_64-64bit
Comment 18 Basuke Suzuki 2018-05-17 20:47:34 PDT
Comment on attachment 340625 [details]
PATCH

View in context: https://bugs.webkit.org/attachment.cgi?id=340625&action=review

>> Source/WebCore/platform/network/curl/CurlRequest.cpp:148
>>      setRequestPaused(false);
> 
> Maybe these if(isCompletedOrCancelled()) should be done inside setRequestPaused

start/suspend/resume/cancel are the gateway from main thread and I think it should be guarded close to those point.

>> Source/WebCore/platform/network/curl/CurlRequest.cpp:157
>>              task(*this, *client);
> 
> Would the following work:
> task(*this, makeRef(*client))

Oh nice.

>> Source/WebCore/platform/network/curl/CurlRequest.cpp:376
>> +            m_handlePaused = true;
> 
> I understand that returning CURL_WRITEFUNC_PAUSE will make curl handle be paused, right?
> If so, that is why m_handlePaused is set to true, right?
> setCallbackPaused(true) is the only needed to update m_isPausedCallback?
> 
> That seems okayish but a bit difficult to understand.

I know. We internally discussed about this very hard and concluded we should send a patch to libcurl repository so that we don't need to manage the state aside with handle. Until then, we keep this ugly . Also good to put a comment here to describe our intent.

>> Source/WebCore/platform/network/curl/CurlRequest.cpp:603
>>              didCompleteTransfer(m_finishedResultCode);
> 
> If you have a lot this pattern:
> if (!m_isSyncRequest)
>    runOnWorkerThread(WTFMove(a))
> else
>   a();
> Maybe it could be made as a method.

There're only two places this pattern appears. Other similar places are slightly different. I am planning to separate the class into CurlSyncRequest and CurlAsyncRequest so that I want to keep them as is.

>> Source/WebCore/platform/network/curl/CurlRequest.cpp:610
>> +        LockHolder lock(m_pauseStateMutex);
> 
> It seems that isPaused is only used in worker thread.
> If m_shouldSuspend and m_isPausedOfRequest are only useful in worker thread, maybe it is just better to use runOnWorkerThreadIfRequired inside setRequestPaused/setCallbackPaused to do the actual state updates.

Let me think. Then we can remove mutex. The communication is paused by two reason. The upper layer's request by suspend/resume (and deferredLoading), and paused to wait client callback result. It is possible that the request to pause may cause no change at all (the return case in the setRequestPaused and setCallbackPaused). We can avoid sending function invocation to worker thread with current mutex implementation. I don't know how often request suspend/resume happens, but the answer may depend on that answer, how often such request happens.

>> Source/WebCore/platform/network/curl/CurlRequest.cpp:672
>> +            invokeCancel();
> 
> Maybe you can just write:
> runOnMainThread([protectedThis = WTFMove(protectedThis)]() {
>      protectedThis->cancel();
> });
> 
> You would need to make the embedding lambda mutable

MSVC hate that code. They cannot compile that. That's why we need this workaround. We are waiting their fix for a while, but still it seems we need more time.

>> Source/WebCore/platform/network/curl/CurlRequest.h:185
>> +    bool m_handlePaused { false };
> 
> I would rename it to m_isHandlePaused.
> Since you want to make sure it is only accessed in worker thread, I would tend to add r/w accessors with debug assertions in it.
> That could make the model clearer.

Make sense.
Comment 19 Basuke Suzuki 2018-05-17 23:45:42 PDT
Created attachment 340683 [details]
Fixed
Comment 20 EWS Watchlist 2018-05-18 06:43:56 PDT
Comment on attachment 340683 [details]
Fixed

Attachment 340683 [details] did not pass win-ews (win):
Output: http://webkit-queues.webkit.org/results/7721544

New failing tests:
http/tests/preload/onload_event.html
Comment 21 EWS Watchlist 2018-05-18 06:44:08 PDT
Created attachment 340691 [details]
Archive of layout-test-results from ews204 for win-future

The attached test failures were seen while running run-webkit-tests on the win-ews.
Bot: ews204  Port: win-future  Platform: CYGWIN_NT-6.1-2.9.0-0.318-5-3-x86_64-64bit
Comment 22 youenn fablet 2018-05-18 08:35:58 PDT
> > Maybe you can just write:
> > runOnMainThread([protectedThis = WTFMove(protectedThis)]() {
> >      protectedThis->cancel();
> > });
> > 
> > You would need to make the embedding lambda mutable
> 
> MSVC hate that code. They cannot compile that. That's why we need this
> workaround. We are waiting their fix for a while, but still it seems we need
> more time.

I thought the issue was about 'this' in lambda.
If so, given that the above code is not making use of 'this', MSVC must be just fine.
Comment 23 youenn fablet 2018-05-18 08:40:31 PDT
Comment on attachment 340683 [details]
Fixed

r=me 

I am not sure using mutex is the right way of doing things.
Please consider whether posting task to the background thread might not be a clearer and stronger approach.

View in context: https://bugs.webkit.org/attachment.cgi?id=340683&action=review

> Source/WebCore/platform/network/curl/CurlRequest.h:188
> +    bool isHandlePaused() const;

I would put these getters above, not in the middle of member definitions.
Comment 24 Basuke Suzuki 2018-05-18 10:30:29 PDT
(In reply to youenn fablet from comment #22)
> > > Maybe you can just write:
> > > runOnMainThread([protectedThis = WTFMove(protectedThis)]() {
> > >      protectedThis->cancel();
> > > });
> > > 
> > > You would need to make the embedding lambda mutable
> > 
> > MSVC hate that code. They cannot compile that. That's why we need this
> > workaround. We are waiting their fix for a while, but still it seems we need
> > more time.
> 
> I thought the issue was about 'this' in lambda.
> If so, given that the above code is not making use of 'this', MSVC must be
> just fine.

Unfortunately no. Even this simpler method cannot be compiled:

void CurlRequest::hello()
{
    auto externalTask = [protectedThis = makeRef(*this)]() {
        auto inernalTask = [protectedThat = makeRef(*protectedThis)]() {
            protectedThat->cancel();
        };
    };
}

d:\src\a.cpp(652): error C2100:
    illegal indirection [D:\WebCore.vcxproj]
d:\src\a.cpp(652): error C2119:
    'protectedThat': the type for 'auto' can not be deduced from an empty initializer [d:\WebCore.vcxproj]
d:\src\a.cpp(653): error C2227:
    left of '->cancel' must point to class/s truct/union/generic type [d:\\WebCore.vcxproj]
Comment 25 Basuke Suzuki 2018-05-18 10:48:02 PDT
Created attachment 340713 [details]
PATCH

youenn, thanks for r+!
Comment 26 WebKit Commit Bot 2018-05-18 11:28:45 PDT
Comment on attachment 340713 [details]
PATCH

Clearing flags on attachment: 340713

Committed r231968: <https://trac.webkit.org/changeset/231968>
Comment 27 WebKit Commit Bot 2018-05-18 11:28:46 PDT
All reviewed patches have been landed.  Closing bug.
Comment 28 Radar WebKit Bug Importer 2018-05-18 11:31:05 PDT
<rdar://problem/40371954>