blob: 0d4860999a0306751707774f686772c0f3557652 [file] [log] [blame]
/*
* Copyright (C) 2013 Apple Inc. All rights reserved.
* Copyright (C) 2017 Sony Interactive Entertainment Inc.
* Copyright (C) 2017 NAVER Corp.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "config.h"
#include "CurlRequestScheduler.h"
#if USE(CURL)
#include "CurlRequestSchedulerClient.h"
namespace WebCore {
CurlRequestScheduler::CurlRequestScheduler(long maxConnects, long maxTotalConnections, long maxHostConnections)
: m_maxConnects(maxConnects)
, m_maxTotalConnections(maxTotalConnections)
, m_maxHostConnections(maxHostConnections)
{
}
bool CurlRequestScheduler::add(CurlRequestSchedulerClient* client)
{
ASSERT(isMainThread());
if (!client)
return false;
startTransfer(client);
startOrWakeUpThread();
return true;
}
void CurlRequestScheduler::cancel(CurlRequestSchedulerClient* client)
{
ASSERT(isMainThread());
if (!client)
return;
cancelTransfer(client);
}
void CurlRequestScheduler::callOnWorkerThread(WTF::Function<void()>&& task)
{
{
Locker locker { m_mutex };
m_taskQueue.append(WTFMove(task));
}
startOrWakeUpThread();
}
void CurlRequestScheduler::startOrWakeUpThread()
{
ASSERT(isMainThread());
{
Locker locker { m_mutex };
if (m_runThread) {
wakeUpThreadIfPossible();
return;
}
}
if (m_thread)
m_thread->waitForCompletion();
{
Locker locker { m_mutex };
m_runThread = true;
}
m_thread = Thread::create("curlThread", [this] {
workerThread();
}, ThreadType::Network);
}
void CurlRequestScheduler::wakeUpThreadIfPossible()
{
Locker locker { m_multiHandleMutex };
if (!m_curlMultiHandle)
return;
m_curlMultiHandle->wakeUp();
}
void CurlRequestScheduler::stopThreadIfNoMoreJobRunning()
{
ASSERT(!isMainThread());
Locker locker { m_mutex };
if (m_activeJobs.size() || m_taskQueue.size())
return;
m_runThread = false;
}
void CurlRequestScheduler::stopThread()
{
{
Locker locker { m_mutex };
m_runThread = false;
}
if (m_thread) {
wakeUpThreadIfPossible();
m_thread->waitForCompletion();
m_thread = nullptr;
}
}
void CurlRequestScheduler::executeTasks()
{
ASSERT(!isMainThread());
Vector<WTF::Function<void()>> taskQueue;
{
Locker locker { m_mutex };
taskQueue = WTFMove(m_taskQueue);
}
for (auto& task : taskQueue)
task();
}
void CurlRequestScheduler::workerThread()
{
ASSERT(!isMainThread());
{
Locker locker { m_multiHandleMutex };
m_curlMultiHandle.emplace();
m_curlMultiHandle->setMaxConnects(m_maxConnects);
m_curlMultiHandle->setMaxTotalConnections(m_maxTotalConnections);
m_curlMultiHandle->setMaxHostConnections(m_maxHostConnections);
}
while (true) {
{
Locker locker { m_mutex };
if (!m_runThread)
break;
}
executeTasks();
const int selectTimeoutMS = INT_MAX;
m_curlMultiHandle->poll({ }, selectTimeoutMS);
int activeCount = 0;
while (m_curlMultiHandle->perform(activeCount) == CURLM_CALL_MULTI_PERFORM) { }
// check the curl messages indicating completed transfers
// and free their resources
while (true) {
int messagesInQueue = 0;
CURLMsg* msg = m_curlMultiHandle->readInfo(messagesInQueue);
if (!msg)
break;
ASSERT(msg->msg == CURLMSG_DONE);
if (auto client = m_clientMaps.inlineGet(msg->easy_handle))
completeTransfer(client, msg->data.result);
}
stopThreadIfNoMoreJobRunning();
}
{
Locker locker { m_multiHandleMutex };
m_curlMultiHandle.reset();
}
}
void CurlRequestScheduler::startTransfer(CurlRequestSchedulerClient* client)
{
client->retain();
auto task = [this, client]() {
CURL* handle = client->setupTransfer();
if (!handle) {
completeTransfer(client, CURLE_FAILED_INIT);
return;
}
m_curlMultiHandle->addHandle(handle);
ASSERT(!m_clientMaps.contains(handle));
m_clientMaps.set(handle, client);
};
Locker locker { m_mutex };
m_activeJobs.add(client);
m_taskQueue.append(WTFMove(task));
}
void CurlRequestScheduler::completeTransfer(CurlRequestSchedulerClient* client, CURLcode result)
{
finalizeTransfer(client, [client, result]() {
client->didCompleteTransfer(result);
});
}
void CurlRequestScheduler::cancelTransfer(CurlRequestSchedulerClient* client)
{
finalizeTransfer(client, [client]() {
client->didCancelTransfer();
});
}
void CurlRequestScheduler::finalizeTransfer(CurlRequestSchedulerClient* client, Function<void()> completionHandler)
{
Locker locker { m_mutex };
if (!m_activeJobs.contains(client))
return;
m_activeJobs.remove(client);
auto task = [this, client, completionHandler = WTFMove(completionHandler)]() {
if (client->handle()) {
ASSERT(m_clientMaps.contains(client->handle()));
m_clientMaps.remove(client->handle());
m_curlMultiHandle->removeHandle(client->handle());
}
completionHandler();
callOnMainThread([client]() {
client->release();
});
};
m_taskQueue.append(WTFMove(task));
}
}
#endif