/* * Copyright (c) 2021, Ali Mohammad Pur * * SPDX-License-Identifier: BSD-2-Clause */ #pragma once #include #include #include #include #include #include #include #include #include namespace RequestServer { enum class CacheLevel { ResolveOnly, CreateConnection, }; } namespace RequestServer::ConnectionCache { template struct Connection { using QueueType = Vector>; using SocketType = Socket; NonnullRefPtr socket; QueueType request_queue; NonnullRefPtr removal_timer; bool has_started { false }; URL current_url {}; Core::ElapsedTimer timer {}; }; struct ConnectionKey { String hostname; u16 port { 0 }; bool operator==(ConnectionKey const&) const = default; }; }; template<> struct AK::Traits : public AK::GenericTraits { static u32 hash(RequestServer::ConnectionCache::ConnectionKey const& key) { return pair_int_hash(key.hostname.hash(), key.port); } }; namespace RequestServer::ConnectionCache { extern HashMap>>> g_tcp_connection_cache; extern HashMap>>> g_tls_connection_cache; void request_did_finish(URL const&, Core::Socket const*); void dump_jobs(); constexpr static inline size_t MaxConcurrentConnectionsPerURL = 2; constexpr static inline size_t ConnectionKeepAliveTimeMilliseconds = 10'000; decltype(auto) get_or_create_connection(auto& cache, URL const& url, auto& job) { using CacheEntryType = RemoveCVReferencevalue)>; auto start_job = [&job](auto& socket) { job.start(socket); }; auto& sockets_for_url = *cache.ensure({ url.host(), url.port_or_default() }, [] { return make(); }); auto it = sockets_for_url.find_if([](auto& connection) { return connection->request_queue.is_empty(); }); auto did_add_new_connection = false; if (it.is_end() && sockets_for_url.size() < ConnectionCache::MaxConcurrentConnectionsPerURL) { using ConnectionType = RemoveCVReferencevalue->at(0))>; sockets_for_url.append(make( ConnectionType::SocketType::construct(nullptr), typename ConnectionType::QueueType {}, Core::Timer::create_single_shot(ConnectionKeepAliveTimeMilliseconds, nullptr))); did_add_new_connection = true; } size_t index; if (it.is_end()) { if (did_add_new_connection) { index = sockets_for_url.size() - 1; } else { // Find the least backed-up connection (based on how many entries are in their request queue. index = 0; auto min_queue_size = (size_t)-1; for (auto it = sockets_for_url.begin(); it != sockets_for_url.end(); ++it) { if (auto queue_size = it->request_queue.size(); min_queue_size > queue_size) { index = it.index(); min_queue_size = queue_size; } } } } else { index = it.index(); } auto& connection = sockets_for_url[index]; if (!connection.has_started) { dbgln("Immediately start request for url {} in {} - {}", url, &connection, connection.socket); connection.has_started = true; connection.removal_timer->stop(); connection.timer.start(); connection.current_url = url; start_job(*connection.socket); } else { dbgln("Enqueue request for URL {} in {} - {}", url, &connection, connection.socket); connection.request_queue.append(move(start_job)); } return connection; } }