Example #1
0
        private static void EndRequest(SafeCurlMultiHandle multiHandle, IntPtr statePtr, int result)
        {
            GCHandle stateHandle          = GCHandle.FromIntPtr(statePtr);
            RequestCompletionSource state = (RequestCompletionSource)stateHandle.Target;

            try
            {
                // No more callbacks so no more data
                state.ResponseMessage.ContentStream.SignalComplete();

                if (CURLcode.CURLE_OK == result)
                {
                    state.TrySetResult(state.ResponseMessage);
                }
                else
                {
                    state.TrySetException(new HttpRequestException(SR.net_http_client_execution_error,
                                                                   GetCurlException(result)));
                }
            }
            catch (Exception ex)
            {
                HandleAsyncException(state, ex);
            }
            finally
            {
                RemoveEasyHandle(multiHandle, stateHandle, true);
            }
        }
Example #2
0
            private void DeactivateActiveRequest(
                SafeCurlMultiHandle multiHandle, EasyRequest easy,
                IntPtr gcHandlePtr, CancellationTokenRegistration cancellationRegistration)
            {
                // Remove the operation from the multi handle so we can shut down the multi handle cleanly
                int removeResult = Interop.libcurl.curl_multi_remove_handle(multiHandle, easy.EasyHandle);

                Debug.Assert(removeResult == CURLMcode.CURLM_OK, "Failed to remove easy handle"); // ignore cleanup errors in release

                // Release the associated GCHandle so that it's not kept alive forever
                if (gcHandlePtr != IntPtr.Zero)
                {
                    try
                    {
                        GCHandle.FromIntPtr(gcHandlePtr).Free();
                        _activeOperations.Remove(gcHandlePtr);
                    }
                    catch (InvalidOperationException)
                    {
                        Debug.Fail("Couldn't get/free the GCHandle for an active operation while shutting down due to failure");
                    }
                }

                // Undo cancellation registration
                cancellationRegistration.Dispose();
            }
Example #3
0
        private static void RemoveEasyHandle(SafeCurlMultiHandle multiHandle, GCHandle stateHandle, bool onMultiStack)
        {
            RequestCompletionSource state         = (RequestCompletionSource)stateHandle.Target;
            SafeCurlHandle          requestHandle = state.RequestHandle;

            if (onMultiStack)
            {
                lock (multiHandle)
                {
                    Interop.libcurl.curl_multi_remove_handle(multiHandle, requestHandle);
                }
                state.SessionHandle = null;
                requestHandle.DangerousRelease();
            }

            if (!state.RequestHeaderHandle.IsInvalid)
            {
                SafeCurlSlistHandle headerHandle = state.RequestHeaderHandle;
                SafeCurlSlistHandle.DisposeAndClearHandle(ref headerHandle);
            }

            SafeCurlHandle.DisposeAndClearHandle(ref requestHandle);

            stateHandle.Free();
        }
Example #4
0
            private void ActivateNewRequest(SafeCurlMultiHandle multiHandle, EasyRequest easy)
            {
                Debug.Assert(easy != null, "We should never get a null request");

                // If cancellation has been requested, complete the request proactively
                if (easy.CancellationToken.IsCancellationRequested)
                {
                    easy.FailRequest(new OperationCanceledException(easy.CancellationToken));
                    easy.Cleanup(); // no active processing remains, so cleanup
                    return;
                }

                // Otherwise, configure it.  Most of the configuration was already done when the EasyRequest
                // was created, but there's additional configuration we need to do specific to this
                // multi agent, specifically telling the easy request about its own GCHandle and setting
                // up callbacks for data processing.  Once it's configured, add it to the multi handle.
                GCHandle gcHandle    = GCHandle.Alloc(easy);
                IntPtr   gcHandlePtr = GCHandle.ToIntPtr(gcHandle);

                try
                {
                    Debug.Assert(easy._associatedMultiAgent == null, "A request should only ever be associated with a single agent");
                    easy._associatedMultiAgent = this;
                    easy.SetCurlOption(CURLoption.CURLOPT_PRIVATE, gcHandlePtr);
                    SetCurlCallbacks(easy, gcHandlePtr);
                    ThrowIfCURLMError(Interop.libcurl.curl_multi_add_handle(multiHandle, easy.EasyHandle));
                }
                catch (Exception exc)
                {
                    gcHandle.Free();
                    easy.FailRequest(exc);
                    easy.Cleanup();  // no active processing remains, so cleanup
                    return;
                }

                // And if cancellation can be requested, hook up a cancellation callback.
                // This callback will put the easy request back into the queue and then
                // ensure that a wake-up request has been issued.  When we pull
                // the easy request out of the request queue, we'll see that it's already
                // associated with this agent, meaning that it's a cancellation request,
                // and we'll deal with it appropriately.
                var cancellationReg = default(CancellationTokenRegistration);

                if (easy.CancellationToken.CanBeCanceled)
                {
                    cancellationReg = easy.CancellationToken.Register(s =>
                    {
                        var state = (Tuple <MultiAgent, EasyRequest>)s;
                        state.Item1.Queue(state.Item2);
                        state.Item1.RequestWakeup();
                    }, Tuple.Create <MultiAgent, EasyRequest>(this, easy));
                }

                // Finally, add it to our map.
                _activeOperations.Add(
                    gcHandlePtr,
                    new ActiveRequest {
                    Easy = easy, CancellationRegistration = cancellationReg
                });
            }
 public static void DisposeAndClearHandle(ref SafeCurlMultiHandle curlHandle)
 {
     if (curlHandle != null)
     {
         curlHandle.Dispose();
         curlHandle = null;
     }
 }
Example #6
0
 internal CurlHandler()
 {
     _multiHandle = Interop.libcurl.curl_multi_init();
     if (_multiHandle.IsInvalid)
     {
         throw new HttpRequestException(SR.net_http_client_execution_error);
     }
     SetCurlMultiOptions();
 }
Example #7
0
 internal CurlHandler()
 {
     _multiHandle = Interop.libcurl.curl_multi_init();
     if (_multiHandle.IsInvalid)
     {
         throw new HttpRequestException(SR.net_http_client_execution_error);
     }
     _multiHandle.Timer = new Timer(CurlTimerElapsed, _multiHandle, Timeout.Infinite, Timeout.Infinite);
     SetCurlMultiOptions();
 }
Example #8
0
        protected override void Dispose(bool disposing)
        {
            if (disposing && !_disposed)
            {
                _disposed = true;
                if (_multiHandlePtr.IsAllocated)
                {
                    _multiHandlePtr.Free();
                }
                _multiHandle = null;
            }

            base.Dispose(disposing);
        }
Example #9
0
        protected override void Dispose(bool disposing)
        {
            if (disposing && !_disposed)
            {
                _disposed = true;
                // TODO: Check for the correct place to free this since
                //       its lifetime should match _multiHandle
                //if (_multiHandlePtr.IsAllocated)
                //{
                //    _multiHandlePtr.Free();
                //}
                _multiHandle = null;
            }

            base.Dispose(disposing);
        }
Example #10
0
            private void FindAndFailActiveRequest(SafeCurlMultiHandle multiHandle, EasyRequest easy, Exception error)
            {
                VerboseTrace("Error: " + error.Message, easy: easy);

                IntPtr        gcHandlePtr;
                ActiveRequest activeRequest;

                if (FindActiveRequest(easy, out gcHandlePtr, out activeRequest))
                {
                    DeactivateActiveRequest(multiHandle, easy, gcHandlePtr, activeRequest.CancellationRegistration);
                    easy.FailRequest(error);
                    easy.Cleanup(); // no active processing remains, so we can cleanup
                }
                else
                {
                    Debug.Assert(easy.Task.IsCompleted, "We should only not be able to find the request if it failed or we started to send back the response.");
                }
            }
Example #11
0
            private void HandleIncomingRequest(SafeCurlMultiHandle multiHandle, IncomingRequest request)
            {
                Debug.Assert(!Monitor.IsEntered(_incomingRequests), "Incoming requests lock should only be held while accessing the queue");
                VerboseTrace("Type: " + request.Type, easy: request.Easy);

                EasyRequest easy = request.Easy;

                switch (request.Type)
                {
                case IncomingRequestType.New:
                    ActivateNewRequest(multiHandle, easy);
                    break;

                case IncomingRequestType.Cancel:
                    Debug.Assert(easy._associatedMultiAgent == this, "Should only cancel associated easy requests");
                    Debug.Assert(easy._cancellationToken.IsCancellationRequested, "Cancellation should have been requested");
                    FindAndFailActiveRequest(multiHandle, easy, new OperationCanceledException(easy._cancellationToken));
                    break;

                case IncomingRequestType.Unpause:
                    Debug.Assert(easy._associatedMultiAgent == this, "Should only unpause associated easy requests");
                    if (!easy._easyHandle.IsClosed)
                    {
                        IntPtr        gcHandlePtr;
                        ActiveRequest ar;
                        Debug.Assert(FindActiveRequest(easy, out gcHandlePtr, out ar), "Couldn't find active request for unpause");

                        int unpauseResult = Interop.libcurl.curl_easy_pause(easy._easyHandle, Interop.libcurl.CURLPAUSE_CONT);
                        try
                        {
                            ThrowIfCURLEError(unpauseResult);
                        }
                        catch (Exception exc)
                        {
                            FindAndFailActiveRequest(multiHandle, easy, exc);
                        }
                    }
                    break;

                default:
                    Debug.Fail("Invalid request type: " + request.Type);
                    break;
                }
            }
Example #12
0
        private static void EndRequest(SafeCurlMultiHandle multiHandle, IntPtr statePtr, int result)
        {
            GCHandle stateHandle          = GCHandle.FromIntPtr(statePtr);
            RequestCompletionSource state = (RequestCompletionSource)stateHandle.Target;

            try
            {
                // No more callbacks so no more data
                state.ResponseMessage.ContentStream.SignalComplete();

                if (CURLcode.CURLE_OK == result)
                {
                    state.TrySetResult(state.ResponseMessage);
                }
                else
                {
                    state.TrySetException(new HttpRequestException(SR.net_http_client_execution_error,
                                                                   GetCurlException(result)));
                }

                if (state.ResponseMessage.StatusCode != HttpStatusCode.Unauthorized && state.Handler.PreAuthenticate)
                {
                    ulong availedAuth;
                    if (Interop.libcurl.curl_easy_getinfo(state.RequestHandle, CURLINFO.CURLINFO_HTTPAUTH_AVAIL, out availedAuth) == CURLcode.CURLE_OK)
                    {
                        state.Handler.AddCredentialToCache(state.RequestMessage.RequestUri, availedAuth, state.NetworkCredential);
                    }
                    // ignoring the exception in this case.
                    // There is no point in killing the request for the sake of putting the credentials into the cache
                }
            }
            catch (Exception ex)
            {
                HandleAsyncException(state, ex);
            }
            finally
            {
                RemoveEasyHandle(multiHandle, stateHandle, true);
            }
        }
            private void FindAndFailActiveRequest(SafeCurlMultiHandle multiHandle, EasyRequest easy, Exception error)
            {
                VerboseTrace("Error: " + error.Message, easy: easy);

                IntPtr gcHandlePtr;
                ActiveRequest activeRequest;
                if (FindActiveRequest(easy, out gcHandlePtr, out activeRequest))
                {
                    DeactivateActiveRequest(multiHandle, easy, gcHandlePtr, activeRequest.CancellationRegistration);
                    easy.FailRequest(error);
                    easy.Cleanup(); // no active processing remains, so we can cleanup
                }
                else
                {
                    Debug.Assert(easy.Task.IsCompleted, "We should only not be able to find the request if it failed or we started to send back the response.");
                }
            }
            private void DeactivateActiveRequest(
                SafeCurlMultiHandle multiHandle, EasyRequest easy, 
                IntPtr gcHandlePtr, CancellationTokenRegistration cancellationRegistration)
            {
                // Remove the operation from the multi handle so we can shut down the multi handle cleanly
                int removeResult = Interop.libcurl.curl_multi_remove_handle(multiHandle, easy._easyHandle);
                Debug.Assert(removeResult == CURLMcode.CURLM_OK, "Failed to remove easy handle"); // ignore cleanup errors in release

                // Release the associated GCHandle so that it's not kept alive forever
                if (gcHandlePtr != IntPtr.Zero)
                {
                    try
                    {
                        GCHandle.FromIntPtr(gcHandlePtr).Free();
                        _activeOperations.Remove(gcHandlePtr);
                    }
                    catch (InvalidOperationException)
                    {
                        Debug.Fail("Couldn't get/free the GCHandle for an active operation while shutting down due to failure");
                    }
                }

                // Undo cancellation registration
                cancellationRegistration.Dispose();
            }
            private void ActivateNewRequest(SafeCurlMultiHandle multiHandle, EasyRequest easy)
            {
                Debug.Assert(easy != null, "We should never get a null request");
                Debug.Assert(easy._associatedMultiAgent == null, "New requests should not be associated with an agent yet");

                // If cancellation has been requested, complete the request proactively
                if (easy._cancellationToken.IsCancellationRequested)
                {
                    easy.FailRequest(new OperationCanceledException(easy._cancellationToken));
                    easy.Cleanup(); // no active processing remains, so cleanup
                    return;
                }

                // Otherwise, configure it.  Most of the configuration was already done when the EasyRequest
                // was created, but there's additional configuration we need to do specific to this
                // multi agent, specifically telling the easy request about its own GCHandle and setting
                // up callbacks for data processing.  Once it's configured, add it to the multi handle.
                GCHandle gcHandle = GCHandle.Alloc(easy);
                IntPtr gcHandlePtr = GCHandle.ToIntPtr(gcHandle);
                try
                {
                    easy._associatedMultiAgent = this;
                    easy.SetCurlOption(CURLoption.CURLOPT_PRIVATE, gcHandlePtr);
                    SetCurlCallbacks(easy, gcHandlePtr);
                    ThrowIfCURLMError(Interop.libcurl.curl_multi_add_handle(multiHandle, easy._easyHandle));
                }
                catch (Exception exc)
                {
                    gcHandle.Free();
                    easy.FailRequest(exc);
                    easy.Cleanup();  // no active processing remains, so cleanup
                    return;
                }

                // And if cancellation can be requested, hook up a cancellation callback.
                // This callback will put the easy request back into the queue, which will
                // ensure that a wake-up request has been issued.  When we pull
                // the easy request out of the request queue, we'll see that it's already
                // associated with this agent, meaning that it's a cancellation request,
                // and we'll deal with it appropriately.
                var cancellationReg = default(CancellationTokenRegistration);
                if (easy._cancellationToken.CanBeCanceled)
                {
                    cancellationReg = easy._cancellationToken.Register(s =>
                    {
                        var state = (Tuple<MultiAgent, EasyRequest>)s;
                        state.Item1.Queue(new IncomingRequest { Easy = state.Item2, Type = IncomingRequestType.Cancel });
                    }, Tuple.Create<MultiAgent, EasyRequest>(this, easy));
                }

                // Finally, add it to our map.
                _activeOperations.Add(
                    gcHandlePtr, 
                    new ActiveRequest { Easy = easy, CancellationRegistration = cancellationReg });
            }
            private void HandleIncomingRequest(SafeCurlMultiHandle multiHandle, IncomingRequest request)
            {
                Debug.Assert(!Monitor.IsEntered(_incomingRequests), "Incoming requests lock should only be held while accessing the queue");
                VerboseTrace("Type: " + request.Type, easy: request.Easy);

                EasyRequest easy = request.Easy;
                switch (request.Type)
                {
                    case IncomingRequestType.New:
                        ActivateNewRequest(multiHandle, easy);
                        break;

                    case IncomingRequestType.Cancel:
                        Debug.Assert(easy._associatedMultiAgent == this, "Should only cancel associated easy requests");
                        Debug.Assert(easy._cancellationToken.IsCancellationRequested, "Cancellation should have been requested");
                        FindAndFailActiveRequest(multiHandle, easy, new OperationCanceledException(easy._cancellationToken));
                        break;

                    case IncomingRequestType.Unpause:
                        Debug.Assert(easy._associatedMultiAgent == this, "Should only unpause associated easy requests");
                        if (!easy._easyHandle.IsClosed)
                        {
                            IntPtr gcHandlePtr;
                            ActiveRequest ar;
                            Debug.Assert(FindActiveRequest(easy, out gcHandlePtr, out ar), "Couldn't find active request for unpause");
                            Debug.Assert(easy._paused == EasyRequest.PauseState.UnpauseRequestIssued, "Unpause requests only make sense when a request has been issued");

                            easy._paused = EasyRequest.PauseState.Unpaused;
                            int unpauseResult = Interop.libcurl.curl_easy_pause(easy._easyHandle, Interop.libcurl.CURLPAUSE_CONT);
                            if (unpauseResult != CURLcode.CURLE_OK)
                            {
                                FindAndFailActiveRequest(multiHandle, easy, new CurlException(unpauseResult, isMulti: false));
                            }
                        }
                        break;

                    default:
                        Debug.Fail("Invalid request type: " + request.Type);
                        break;
                }
            }
Example #17
0
            private void HandleIncomingRequest(SafeCurlMultiHandle multiHandle, IncomingRequest request)
            {
                Debug.Assert(!Monitor.IsEntered(_incomingRequests), "Incoming requests lock should only be held while accessing the queue");
                VerboseTrace("Type: " + request.Type, easy: request.Easy);

                EasyRequest easy = request.Easy;
                switch (request.Type)
                {
                    case IncomingRequestType.New:
                        ActivateNewRequest(multiHandle, easy);
                        break;

                    case IncomingRequestType.Cancel:
                        Debug.Assert(easy._associatedMultiAgent == this, "Should only cancel associated easy requests");
                        Debug.Assert(easy._cancellationToken.IsCancellationRequested, "Cancellation should have been requested");
                        FindAndFailActiveRequest(multiHandle, easy, new OperationCanceledException(easy._cancellationToken));
                        break;

                    case IncomingRequestType.Unpause:
                        Debug.Assert(easy._associatedMultiAgent == this, "Should only unpause associated easy requests");
                        if (!easy._easyHandle.IsClosed)
                        {
                            IntPtr gcHandlePtr;
                            ActiveRequest ar;
                            Debug.Assert(FindActiveRequest(easy, out gcHandlePtr, out ar), "Couldn't find active request for unpause");

                            CURLcode unpauseResult = Interop.Http.EasyUnpause(easy._easyHandle);
                            try
                            {
                                ThrowIfCURLEError(unpauseResult);
                            }
                            catch (Exception exc)
                            {
                                FindAndFailActiveRequest(multiHandle, easy, exc);
                            }
                        }
                        break;

                    default:
                        Debug.Fail("Invalid request type: " + request.Type);
                        break;
                }
            }
Example #18
0
        private static void PollFunction(SafeCurlMultiHandle multiHandle)
        {
            List<Interop.libc.pollfd> fds = new List<Interop.libc.pollfd>();

            while (true)
            {
                lock (multiHandle)
                {
                    if (0 == multiHandle.RequestCount)
                    {
                        multiHandle.PollCancelled = true;
                        return;
                    }
                }

                multiHandle.PollFds(fds);
                if (0 == fds.Count)
                {
                    // No read/write activity on the sockets.. keep polling
                    continue;
                }

                int result = -1;
                for (int i = 0; i < fds.Count; i++)
                {
                    int eventBitMask = ((fds[i].revents & PollFlags.POLLIN) != 0) ? CurlSelect.CURL_CSELECT_IN : 0;
                    eventBitMask |= ((fds[i].revents & PollFlags.POLLOUT) != 0) ? CurlSelect.CURL_CSELECT_OUT : 0;
                    if (eventBitMask != 0)
                    {
                        lock (multiHandle)
                        {
                            int runningTransfers;
                            if (CURLMcode.CURLM_OK ==
                                Interop.libcurl.curl_multi_socket_action(multiHandle, fds[i].fd, eventBitMask,
                                    out runningTransfers))
                            {
                                result = CURLMcode.CURLM_OK;
                                if (0 == runningTransfers)
                                {
                                    multiHandle.Timer.Change(Timeout.Infinite, Timeout.Infinite);
                                }
                            }
                            // Ignore errors
                        }
                    }
                }

                if (CURLMcode.CURLM_OK == result)
                {
                    CheckForCompletedTransfers(multiHandle);
                }
            }
        }
Example #19
0
            private void WorkerLoop()
            {
                Debug.Assert(!Monitor.IsEntered(_incomingRequests), "No locks should be held while invoking Process");
                Debug.Assert(_runningWorker != null && _runningWorker.Id == Task.CurrentId, "This is the worker, so it must be running");
                Debug.Assert(_wakeupRequestedPipeFd != 0, "Should have a valid pipe for wake ups");

                // Create the multi handle to use for this round of processing.  This one handle will be used
                // to service all easy requests currently available and all those that come in while
                // we're processing other requests.  Once the work quiesces and there are no more requests
                // to process, this multi handle will be released as the worker goes away.  The next
                // time a request arrives and a new worker is spun up, a new multi handle will be created.
                SafeCurlMultiHandle multiHandle = Interop.libcurl.curl_multi_init();

                if (multiHandle.IsInvalid)
                {
                    throw CreateHttpRequestException();
                }

                // Clear our active operations table.  This should already be clear, either because
                // all previous operations completed without unexpected exception, or in the case of an
                // unexpected exception we should have cleaned up gracefully anyway.  But just in case...
                Debug.Assert(_activeOperations.Count == 0, "We shouldn't have any active operations when starting processing.");
                _activeOperations.Clear();

                bool endingSuccessfully = false;

                try
                {
                    // Continue processing as long as there are any active operations
                    while (true)
                    {
                        // First handle any requests in the incoming requests queue.
                        while (true)
                        {
                            IncomingRequest request;
                            lock (_incomingRequests)
                            {
                                if (_incomingRequests.Count == 0)
                                {
                                    break;
                                }
                                request = _incomingRequests.Dequeue();
                            }
                            HandleIncomingRequest(multiHandle, request);
                        }

                        // If we have no active operations, we're done.
                        if (_activeOperations.Count == 0)
                        {
                            endingSuccessfully = true;
                            return;
                        }

                        // We have one or more active operations. Run any work that needs to be run.
                        int running_handles;
                        ThrowIfCURLMError(Interop.libcurl.curl_multi_perform(multiHandle, out running_handles));

                        // Complete and remove any requests that have finished being processed.
                        int    pendingMessages;
                        IntPtr messagePtr;
                        while ((messagePtr = Interop.libcurl.curl_multi_info_read(multiHandle, out pendingMessages)) != IntPtr.Zero)
                        {
                            Interop.libcurl.CURLMsg message = Marshal.PtrToStructure <Interop.libcurl.CURLMsg>(messagePtr);
                            Debug.Assert(message.msg == Interop.libcurl.CURLMSG.CURLMSG_DONE, "CURLMSG_DONE is supposed to be the only message type");

                            IntPtr        gcHandlePtr;
                            ActiveRequest completedOperation;
                            if (message.msg == Interop.libcurl.CURLMSG.CURLMSG_DONE)
                            {
                                int getInfoResult = Interop.libcurl.curl_easy_getinfo(message.easy_handle, CURLINFO.CURLINFO_PRIVATE, out gcHandlePtr);
                                Debug.Assert(getInfoResult == CURLcode.CURLE_OK, "Failed to get info on a completing easy handle");
                                if (getInfoResult == CURLcode.CURLE_OK)
                                {
                                    bool gotActiveOp = _activeOperations.TryGetValue(gcHandlePtr, out completedOperation);
                                    Debug.Assert(gotActiveOp, "Expected to find GCHandle ptr in active operations table");
                                    if (gotActiveOp)
                                    {
                                        DeactivateActiveRequest(multiHandle, completedOperation.Easy, gcHandlePtr, completedOperation.CancellationRegistration);
                                        FinishRequest(completedOperation.Easy, message.result);
                                    }
                                }
                            }
                        }

                        // Wait for more things to do.  Even with our cancellation mechanism, we specify a timeout so that
                        // just in case something goes wrong we can recover gracefully.  This timeout is relatively long.
                        // Note, though, that libcurl has its own internal timeout, which can be requested separately
                        // via curl_multi_timeout, but which is used implicitly by curl_multi_wait if it's shorter
                        // than the value we provide.
                        const int FailsafeTimeoutMilliseconds = 1000;
                        int       numFds;
                        unsafe
                        {
                            Interop.libcurl.curl_waitfd extraFds = new Interop.libcurl.curl_waitfd {
                                fd      = _wakeupRequestedPipeFd,
                                events  = Interop.libcurl.CURL_WAIT_POLLIN,
                                revents = 0
                            };
                            ThrowIfCURLMError(Interop.libcurl.curl_multi_wait(multiHandle, &extraFds, 1, FailsafeTimeoutMilliseconds, out numFds));
                            if ((extraFds.revents & Interop.libcurl.CURL_WAIT_POLLIN) != 0)
                            {
                                // We woke up (at least in part) because a wake-up was requested.
                                // Read the data out of the pipe to clear it.
                                Debug.Assert(numFds >= 1, "numFds should have been at least one, as the extraFd was set");
                                VerboseTrace("curl_multi_wait wake-up notification");
                                ReadFromWakeupPipeWhenKnownToContainData();
                            }
                            VerboseTraceIf(numFds == 0, "curl_multi_wait timeout");
                        }

                        // PERF NOTE: curl_multi_wait uses poll (assuming it's available), which is O(N) in terms of the number of fds
                        // being waited on. If this ends up being a scalability bottleneck, we can look into using the curl_multi_socket_*
                        // APIs, which would let us switch to using epoll by being notified when sockets file descriptors are added or
                        // removed and configuring the epoll context with EPOLL_CTL_ADD/DEL, which at the expense of a lot of additional
                        // complexity would let us turn the O(N) operation into an O(1) operation.  The additional complexity would come
                        // not only in the form of additional callbacks and managing the socket collection, but also in the form of timer
                        // management, which is necessary when using the curl_multi_socket_* APIs and which we avoid by using just
                        // curl_multi_wait/perform.
                    }
                }
                finally
                {
                    // If we got an unexpected exception, something very bad happened. We may have some
                    // operations that we initiated but that weren't completed. Make sure to clean up any
                    // such operations, failing them and releasing their resources.
                    if (_activeOperations.Count > 0)
                    {
                        Debug.Assert(!endingSuccessfully, "We should only have remaining operations if we got an unexpected exception");
                        foreach (KeyValuePair <IntPtr, ActiveRequest> pair in _activeOperations)
                        {
                            ActiveRequest failingOperation         = pair.Value;
                            IntPtr        failingOperationGcHandle = pair.Key;

                            DeactivateActiveRequest(multiHandle, failingOperation.Easy, failingOperationGcHandle, failingOperation.CancellationRegistration);

                            // Complete the operation's task and clean up any of its resources
                            failingOperation.Easy.FailRequest(CreateHttpRequestException());
                            failingOperation.Easy.Cleanup(); // no active processing remains, so cleanup
                        }

                        // Clear the table.
                        _activeOperations.Clear();
                    }

                    // Finally, dispose of the multi handle.
                    multiHandle.Dispose();
                }
            }
Example #20
0
        private static void CheckForCompletedTransfers(SafeCurlMultiHandle multiHandle)
        {
            int pendingMessages;
            IntPtr messagePtr;

            lock (multiHandle)
            {
                messagePtr = Interop.libcurl.curl_multi_info_read(multiHandle, out pendingMessages);
            }
            while (IntPtr.Zero != messagePtr)
            {
                var message = Marshal.PtrToStructure<Interop.libcurl.CURLMsg>(messagePtr);
                if (Interop.libcurl.CURLMSG.CURLMSG_DONE == message.msg)
                {
                    IntPtr statePtr;
                    int result = Interop.libcurl.curl_easy_getinfo(message.easy_handle, CURLINFO.CURLINFO_PRIVATE, out statePtr);
                    if (result == CURLcode.CURLE_OK)
                    {
                        EndRequest(multiHandle, statePtr, message.result);
                    }
                }
                lock (multiHandle)
                {
                    messagePtr = Interop.libcurl.curl_multi_info_read(multiHandle, out pendingMessages);
                }
            }
        }
Example #21
0
        private static void CheckForCompletedTransfers(SafeCurlMultiHandle multiHandle, int socketFd, int eventBitMask, IntPtr statePtr)
        {
            int runningTransfers;
            // TODO: Revisit the lock and see if serialization is really needed
            lock (multiHandle)
            {
                int result = Interop.libcurl.curl_multi_socket_action(multiHandle, socketFd, eventBitMask,
                    out runningTransfers);
                if (result != CURLMcode.CURLM_OK)
                {
                    throw new HttpRequestException(SR.net_http_client_execution_error, GetCurlException(result, true));
                }
            }

            // Let socket callback handle fd-specific message
            if (IntPtr.Zero == statePtr)
            {
                return;
            }

            int pendingMessages;
            IntPtr messagePtr;
            lock (multiHandle)
            {
                messagePtr = Interop.libcurl.curl_multi_info_read(multiHandle, out pendingMessages);
            }
            while (IntPtr.Zero != messagePtr)
            {
                var message = Marshal.PtrToStructure<Interop.libcurl.CURLMsg>(messagePtr);
                if (Interop.libcurl.CURLMSG.CURLMSG_DONE == message.msg)
                {
                    EndRequest(multiHandle, statePtr, message.result);
                }
                lock (multiHandle)
                {
                    messagePtr = Interop.libcurl.curl_multi_info_read(multiHandle, out pendingMessages);
                }
            }
        }
Example #22
0
            private void Process()
            {
                Debug.Assert(!Monitor.IsEntered(_incomingRequests), "No locks should be held while invoking Process");
                Debug.Assert(_workerRunning, "This is the worker, so it must be running");
                Debug.Assert(_wakeupRequestedPipeFd != 0, "Should have a valid pipe for wake ups");

                // Create the multi handle to use for this round of processing.  This one handle will be used
                // to service all easy requests currently available and all those that come in while
                // we're processing other requests.  Once the work quiesces and there are no more requests
                // to process, this multi handle will be released as the worker goes away.  The next
                // time a request arrives and a new worker is spun up, a new multi handle will be created.
                SafeCurlMultiHandle multiHandle = Interop.libcurl.curl_multi_init();

                if (multiHandle.IsInvalid)
                {
                    throw CreateHttpRequestException();
                }

                // Clear our active operations table.  This should already be clear, either because
                // all previous operations completed without unexpected exception, or in the case of an
                // unexpected exception we should have cleaned up gracefully anyway.  But just in case...
                Debug.Assert(_activeOperations.Count == 0, "We shouldn't have any active operations when starting processing.");
                _activeOperations.Clear();

                bool endingSuccessfully = false;

                try
                {
                    // Continue processing as long as there are any active operations
                    while (true)
                    {
                        // Activate any new operations that were submitted, and cancel any operations
                        // that should no longer be around.
                        lock (_incomingRequests)
                        {
                            while (_incomingRequests.Count > 0)
                            {
                                EasyRequest easy = _incomingRequests.Dequeue();
                                Debug.Assert(easy._associatedMultiAgent == null || easy._associatedMultiAgent == this, "An incoming request must only be associated with no or this agent");
                                if (easy._associatedMultiAgent == null)
                                {
                                    // Handle new request
                                    ActivateNewRequest(multiHandle, easy);
                                }
                                else
                                {
                                    // Handle cancellation request.
                                    Debug.Assert(easy.CancellationToken.IsCancellationRequested, "_associatedMultiAgent should only be non-null if cancellation was requested");
                                    IntPtr        gcHandlePtr;
                                    ActiveRequest activeRequest;
                                    if (FindActiveRequest(easy, out gcHandlePtr, out activeRequest))
                                    {
                                        DeactivateActiveRequest(multiHandle, easy, gcHandlePtr, activeRequest.CancellationRegistration);
                                        easy.FailRequest(new OperationCanceledException(easy.CancellationToken));
                                        easy.Cleanup(); // no active processing remains, so we can cleanup
                                    }
                                    else
                                    {
                                        Debug.Assert(easy.Task.IsCompleted, "We should only not be able to find the request if it was already completed.");
                                    }
                                }
                            }
                        }

                        // If we have no active operations, we're done.
                        if (_activeOperations.Count == 0)
                        {
                            endingSuccessfully = true;
                            return;
                        }

                        // We have one or more active operaitons. Run any work that needs to be run.
                        int running_handles;
                        ThrowIfCURLMError(Interop.libcurl.curl_multi_perform(multiHandle, out running_handles));

                        // Complete and remove any requests that have finished being processed.
                        int    pendingMessages;
                        IntPtr messagePtr;
                        while ((messagePtr = Interop.libcurl.curl_multi_info_read(multiHandle, out pendingMessages)) != IntPtr.Zero)
                        {
                            Interop.libcurl.CURLMsg message = Marshal.PtrToStructure <Interop.libcurl.CURLMsg>(messagePtr);
                            IntPtr        gcHandlePtr;
                            ActiveRequest completedOperation;
                            if (message.msg == Interop.libcurl.CURLMSG.CURLMSG_DONE &&
                                Interop.libcurl.curl_easy_getinfo(message.easy_handle, CURLINFO.CURLINFO_PRIVATE, out gcHandlePtr) == CURLcode.CURLE_OK &&
                                _activeOperations.TryGetValue(gcHandlePtr, out completedOperation))
                            {
                                DeactivateActiveRequest(multiHandle, completedOperation.Easy, gcHandlePtr, completedOperation.CancellationRegistration);
                                FinishRequest(completedOperation.Easy, message.result);
                            }
                        }

                        // Wait for more things to do.  Even with our cancellation mechanism, we specify a timeout so that
                        // just in case something goes wrong we can recover gracefully.  This timeout is relatively long.
                        // Note, though, that libcurl has its own internal timeout, which can be requested separately
                        // via curl_multi_timeout, but which is used implicitly by curl_multi_wait if it's shorter
                        // than the value we provide.
                        const int FailsafeTimeoutMilliseconds = 1000;
                        int       numFds;
                        unsafe
                        {
                            Interop.libcurl.curl_waitfd extraFds = new Interop.libcurl.curl_waitfd {
                                fd      = _wakeupRequestedPipeFd,
                                events  = Interop.libcurl.CURL_WAIT_POLLIN,
                                revents = 0
                            };
                            ThrowIfCURLMError(Interop.libcurl.curl_multi_wait(multiHandle, &extraFds, 1, FailsafeTimeoutMilliseconds, out numFds));
                            if ((extraFds.revents & Interop.libcurl.CURL_WAIT_POLLIN) != 0)
                            {
                                // We woke up (at least in part) because a wake-up was requested.  Read the data out of the pipe
                                // to clear it. It's possible but unlikely that there will be tons of extra data in the pipe,
                                // more than we end up reading out here (it's unlikely because we only write a byte to the pipe when
                                // transitioning from 0 to 1 incoming request or when cancellation is requested, and that would
                                // need to happen many times in a single iteration).  In that unlikely case, we'll simply loop
                                // around again as normal and end up waking up spuriously from the next curl_multi_wait.  For now,
                                // this is preferable to making additional syscalls to poll and read from the pipe).
                                const int ClearBufferSize = 4096; // some sufficiently large size to clear the pipe in any normal case
                                byte *    clearBuf        = stackalloc byte[ClearBufferSize];
                                while (Interop.CheckIo((long)Interop.libc.read(_wakeupRequestedPipeFd, clearBuf, (IntPtr)ClearBufferSize)))
                                {
                                    ;
                                }
                            }
                        }

                        // PERF NOTE: curl_multi_wait uses poll (assuming it's available), which is O(N) in terms of the number of fds
                        // being waited on. If this ends up being a scalability bottleneck, we can look into using the curl_multi_socket_*
                        // APIs, which would let us switch to using epoll by being notified when sockets file descriptors are added or
                        // removed and configuring the epoll context with EPOLL_CTL_ADD/DEL, which at the expense of a lot of additional
                        // complexity would let us turn the O(N) operation into an O(1) operation.  The additional complexity would come
                        // not only in the form of additional callbacks and managing the socket collection, but also in the form of timer
                        // management, which is necessary when using the curl_multi_socket_* APIs and which we avoid by using just
                        // curl_multi_wait/perform.
                    }
                }
                finally
                {
                    // If we got an unexpected exception, something very bad happened. We may have some
                    // operations that we initiated but that weren't completed. Make sure to clean up any
                    // such operations, failing them and releasing their resources.
                    if (_activeOperations.Count > 0)
                    {
                        Debug.Assert(!endingSuccessfully, "We should only have remaining operations if we got an unexpected exception");
                        foreach (KeyValuePair <IntPtr, ActiveRequest> pair in _activeOperations)
                        {
                            ActiveRequest failingOperation         = pair.Value;
                            IntPtr        failingOperationGcHandle = pair.Key;

                            DeactivateActiveRequest(multiHandle, failingOperation.Easy, failingOperationGcHandle, failingOperation.CancellationRegistration);

                            // Complete the operation's task and clean up any of its resources
                            failingOperation.Easy.FailRequest(CreateHttpRequestException());
                            failingOperation.Easy.Cleanup(); // no active processing remains, so cleanup
                        }

                        // Clear the table.
                        _activeOperations.Clear();
                    }

                    // Finally, dispose of the multi handle.
                    multiHandle.Dispose();
                }
            }