1/*
2 * Copyright 2010-2017 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License").
5 * You may not use this file except in compliance with the License.
6 * A copy of the License is located at
7 *
8 * http://aws.amazon.com/apache2.0
9 *
10 * or in the "license" file accompanying this file. This file is distributed
11 * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12 * express or implied. See the License for the specific language governing
13 * permissions and limitations under the License.
14 */
15
16#include <aws/core/http/curl/CurlHandleContainer.h>
17#include <aws/core/utils/logging/LogMacros.h>
18
19#include <algorithm>
20
21using namespace Aws::Utils::Logging;
22using namespace Aws::Http;
23
24static const char* CURL_HANDLE_CONTAINER_TAG = "CurlHandleContainer";
25
26
27CurlHandleContainer::CurlHandleContainer(unsigned maxSize, long httpRequestTimeout, long connectTimeout, bool enableTcpKeepAlive,
28 unsigned long tcpKeepAliveIntervalMs, long lowSpeedTime, unsigned long lowSpeedLimit) :
29 m_maxPoolSize(maxSize), m_httpRequestTimeout(httpRequestTimeout), m_connectTimeout(connectTimeout), m_enableTcpKeepAlive(enableTcpKeepAlive),
30 m_tcpKeepAliveIntervalMs(tcpKeepAliveIntervalMs), m_lowSpeedTime(lowSpeedTime), m_lowSpeedLimit(lowSpeedLimit), m_poolSize(0)
31{
32 AWS_LOGSTREAM_INFO(CURL_HANDLE_CONTAINER_TAG, "Initializing CurlHandleContainer with size " << maxSize);
33}
34
35CurlHandleContainer::~CurlHandleContainer()
36{
37 AWS_LOGSTREAM_INFO(CURL_HANDLE_CONTAINER_TAG, "Cleaning up CurlHandleContainer.");
38 for (CURL* handle : m_handleContainer.ShutdownAndWait(m_poolSize))
39 {
40 AWS_LOGSTREAM_DEBUG(CURL_HANDLE_CONTAINER_TAG, "Cleaning up " << handle);
41 curl_easy_cleanup(handle);
42 }
43}
44
45CURL* CurlHandleContainer::AcquireCurlHandle()
46{
47 AWS_LOGSTREAM_DEBUG(CURL_HANDLE_CONTAINER_TAG, "Attempting to acquire curl connection.");
48
49 if(!m_handleContainer.HasResourcesAvailable())
50 {
51 AWS_LOGSTREAM_DEBUG(CURL_HANDLE_CONTAINER_TAG, "No current connections available in pool. Attempting to create new connections.");
52 CheckAndGrowPool();
53 }
54
55 CURL* handle = m_handleContainer.Acquire();
56 AWS_LOGSTREAM_INFO(CURL_HANDLE_CONTAINER_TAG, "Connection has been released. Continuing.");
57 AWS_LOGSTREAM_DEBUG(CURL_HANDLE_CONTAINER_TAG, "Returning connection handle " << handle);
58 return handle;
59}
60
61void CurlHandleContainer::ReleaseCurlHandle(CURL* handle)
62{
63 if (handle)
64 {
65 curl_easy_reset(handle);
66 SetDefaultOptionsOnHandle(handle);
67 AWS_LOGSTREAM_DEBUG(CURL_HANDLE_CONTAINER_TAG, "Releasing curl handle " << handle);
68 m_handleContainer.Release(handle);
69 AWS_LOGSTREAM_DEBUG(CURL_HANDLE_CONTAINER_TAG, "Notified waiting threads.");
70 }
71}
72
73void CurlHandleContainer::DestroyCurlHandle(CURL* handle)
74{
75 if (!handle)
76 {
77 return;
78 }
79
80 curl_easy_cleanup(handle);
81 {
82 std::lock_guard<std::mutex> locker(m_containerLock);
83 m_poolSize--;
84 }
85 AWS_LOGSTREAM_DEBUG(CURL_HANDLE_CONTAINER_TAG, "Destroy curl handle: " << handle << " and decrease pool size by 1.");
86}
87
88bool CurlHandleContainer::CheckAndGrowPool()
89{
90 std::lock_guard<std::mutex> locker(m_containerLock);
91 if (m_poolSize < m_maxPoolSize)
92 {
93 unsigned multiplier = m_poolSize > 0 ? m_poolSize : 1;
94 unsigned amountToAdd = (std::min)(multiplier * 2, m_maxPoolSize - m_poolSize);
95 AWS_LOGSTREAM_DEBUG(CURL_HANDLE_CONTAINER_TAG, "attempting to grow pool size by " << amountToAdd);
96
97 unsigned actuallyAdded = 0;
98 for (unsigned i = 0; i < amountToAdd; ++i)
99 {
100 CURL* curlHandle = curl_easy_init();
101
102 if (curlHandle)
103 {
104 SetDefaultOptionsOnHandle(curlHandle);
105 m_handleContainer.Release(curlHandle);
106 ++actuallyAdded;
107 }
108 else
109 {
110 AWS_LOGSTREAM_ERROR(CURL_HANDLE_CONTAINER_TAG, "curl_easy_init failed to allocate.");
111 break;
112 }
113 }
114
115 AWS_LOGSTREAM_INFO(CURL_HANDLE_CONTAINER_TAG, "Pool grown by " << actuallyAdded);
116 m_poolSize += actuallyAdded;
117
118 return actuallyAdded > 0;
119 }
120
121 AWS_LOGSTREAM_INFO(CURL_HANDLE_CONTAINER_TAG, "Pool cannot be grown any further, already at max size.");
122
123 return false;
124}
125
126void CurlHandleContainer::SetDefaultOptionsOnHandle(CURL* handle)
127{
128 //for timeouts to work in a multi-threaded context,
129 //always turn signals off. This also forces dns queries to
130 //not be included in the timeout calculations.
131 curl_easy_setopt(handle, CURLOPT_NOSIGNAL, 1L);
132 curl_easy_setopt(handle, CURLOPT_TIMEOUT_MS, m_httpRequestTimeout);
133 curl_easy_setopt(handle, CURLOPT_CONNECTTIMEOUT_MS, m_connectTimeout);
134 curl_easy_setopt(handle, CURLOPT_LOW_SPEED_LIMIT, m_lowSpeedLimit);
135 curl_easy_setopt(handle, CURLOPT_LOW_SPEED_TIME, m_lowSpeedTime < 1000 ? (m_lowSpeedTime == 0 ? 0 : 1) : m_lowSpeedTime / 1000);
136 curl_easy_setopt(handle, CURLOPT_TCP_KEEPALIVE, m_enableTcpKeepAlive ? 1L : 0L);
137 curl_easy_setopt(handle, CURLOPT_TCP_KEEPINTVL, m_tcpKeepAliveIntervalMs);
138 curl_easy_setopt(handle, CURLOPT_TCP_KEEPIDLE, m_tcpKeepAliveIntervalMs);
139#ifdef CURL_HAS_H2
140 curl_easy_setopt(handle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2_0);
141#endif
142}
143