Skip to content
File

Blob: src/workerd/io/hibernation-manager.h

cpp217 lines
1// Copyright (c) 2017-2023 Cloudflare, Inc.
2// Licensed under the Apache 2.0 license found in the LICENSE file or at:
3// https://opensource.org/licenses/Apache-2.0
4 
5#pragma once
6 
7#include <workerd/api/actor-state.h>
8#include <workerd/api/hibernatable-web-socket.h>
9#include <workerd/api/web-socket.h>
10#include <workerd/jsg/jsg.h>
11 
12#include <kj/exception.h>
13 
14#include <list>
15 
16namespace workerd {
17 
18// Implements the HibernationManager class.
19class HibernationManagerImpl final: public Worker::Actor::HibernationManager {
20 public:
21 HibernationManagerImpl(kj::Own<Worker::Actor::Loopback> loopback, uint16_t hibernationEventType);
22 ~HibernationManagerImpl() noexcept(false);
23 
24 // Tells the HibernationManager to create a new HibernatableWebSocket with the associated tags
25 // and to initiate the `readLoop()` for this websocket. The `tags` array *must* contain only
26 // unique elements.
27 void acceptWebSocket(jsg::Ref<api::WebSocket> ws, kj::ArrayPtr<kj::String> tags) override;
28 
29 // Gets a collection of websockets associated with the given tag. Any hibernating websockets will
30 // be woken up. If no tag is provided, we return all accepted websockets.
31 kj::Vector<jsg::Ref<api::WebSocket>> getWebSockets(
32 jsg::Lock& js, kj::Maybe<kj::StringPtr> tag) override;
33 
34 // Hibernates all the websockets held by the HibernationManager.
35 // This converts our activeOrPackage from an api::WebSocket to a HibernationPackage.
36 void hibernateWebSockets(Worker::Lock& lock) override;
37 
38 void setWebSocketAutoResponse(
39 kj::Maybe<kj::StringPtr> request, kj::Maybe<kj::StringPtr> response) override;
40 kj::Maybe<jsg::Ref<api::WebSocketRequestResponsePair>> getWebSocketAutoResponse(
41 jsg::Lock& js) override;
42 void setTimerChannel(TimerChannel& timerChannel) override;
43 
44 kj::Own<HibernationManager> addRef() override;
45 
46 friend class api::HibernatableWebSocketEvent;
47 
48 // Sets/Unset the maximum time in milliseconds that an hibernatable websocket event can run for.
49 // If the timeout is reached, event is canceled.
50 void setEventTimeout(kj::Maybe<uint32_t> timeoutMs) override;
51 
52 // Gets the event timeout if set.
53 kj::Maybe<uint32_t> getEventTimeout() override;
54 
55 private:
56 class HibernatableWebSocket;
57 
58 kj::Promise<void> handleReadLoop(HibernatableWebSocket& refToHibernatable);
59 
60 // Each HibernatableWebSocket can have multiple tags, so we want to store a reference
61 // in our kj::List.
62 struct TagListItem {
63 kj::Maybe<HibernatableWebSocket&> hibWS;
64 kj::ListLink<TagListItem> link;
65 kj::StringPtr tag;
66 // The List that refers to this TagListItem.
67 // If `list` is null, we've already removed this item from the list.
68 kj::Maybe<kj::List<TagListItem, &TagListItem::link>&> list;
69 };
70 
71 // api::WebSockets cannot survive hibernation, but kj::WebSockets do. This class helps us
72 // manage the transition of an api::WebSocket from its active state to a hibernated state
73 // and vice versa.
74 //
75 // Some properties of the JS websocket object need to be retained throughout hibernation,
76 // such as `attachment`, `url`, `extensions`, etc. These properties are only read/modified
77 // when initiating, or waking from hibernation.
78 class HibernatableWebSocket {
79 public:
80 HibernatableWebSocket(jsg::Ref<api::WebSocket> websocket,
81 kj::ArrayPtr<kj::String> tags,
82 HibernationManagerImpl& manager);
83 ~HibernatableWebSocket() noexcept(false);
84 KJ_DISALLOW_COPY_AND_MOVE(HibernatableWebSocket);
85 
86 // Returns the tags associated with this HibernatableWebSocket.
87 kj::Array<kj::StringPtr> getTags();
88 
89 // Returns the tags associated with this HibernatableWebSocket.
90 // Note that this returns an array of Strings, unlike `getTags()`.
91 // Copying the strings each time tags are requested would be expensive,
92 // so we only do it when we're delivering a close/error event because
93 // we will be destroying the HibernatableWebSocket object,
94 // which the tags need to outlive.
95 kj::Array<kj::String> cloneTags();
96 
97 // Returns a reference to the active websocket. If the websocket is currently hibernating,
98 // we have to unhibernate it first. The process moves values from the HibernatableWebSocket
99 // to the api::WebSocket.
100 jsg::Ref<api::WebSocket> getActiveOrUnhibernate(jsg::Lock& js);
101 
102 kj::ListLink<HibernatableWebSocket> link;
103 
104 // An array of all the items/nodes that refer to this HibernatableWebSocket.
105 // Keeping track of these items allows us to quickly remove every reference from `tagToWs`
106 // once the websocket disconnects -- rather than iterating through each relevant tag in the
107 // hashmap and removing it from each kj::List.
108 kj::Array<TagListItem> tagItems;
109 
110 // If active, we have an api::WebSocket reference, otherwise, we're hibernating, so we retain
111 // the websocket's properties in a HibernationPackage until it's time to wake up.
112 kj::OneOf<jsg::Ref<api::WebSocket>, api::WebSocket::HibernationPackage> activeOrPackage;
113 
114 // This is an owned websocket that we extract from the api::WebSocket after accepting as
115 // hibernatable. It becomes null once we dispatch a close or error event because we want its
116 // lifetime to be managed by IoContext's DeleteQueue. This helps prevent a situation where the
117 // HibernationManager drops the websocket before all queued messages have sent.
118 kj::Maybe<kj::Own<kj::WebSocket>> ws;
119 
120 HibernationManagerImpl& manager;
121 // TODO(someday): We (currently) only use the HibernationManagerImpl reference to refer to
122 // `tagToWs` when running the dtor for `HibernatableWebSocket`. This feels a bit excessive,
123 // I would rather have the HibernationManager deal with its collections than have the
124 // HibernatableWebSocket do so. Maybe come back to this at some point?
125 
126 // Reference to the Node in `allWs` that allows us to do fast deletion on disconnect.
127 kj::Maybe<std::list<kj::Own<HibernatableWebSocket>>::iterator> node;
128 
129 // True once we have dispatched the close event.
130 // This prevents us from dispatching it if we have already done so.
131 bool hasDispatchedClose = false;
132 
133 // Stores the last received autoResponseRequest timestamp.
134 kj::Maybe<kj::Date> autoResponseTimestamp;
135 
136 // Keeps track of the currently ongoing websocket auto-response send promise. This promise may
137 // be moved to api::websocket if an hibernating websocket unhibernates.
138 kj::Promise<void> autoResponsePromise = kj::READY_NOW;
139 
140 friend HibernationManagerImpl;
141 };
142 
143 // Removes a HibernatableWebSocket from the HibernationManager's various collections.
144 void dropHibernatableWebSocket(HibernatableWebSocket& hib);
145 
146 // Removes the HibernatableWebSocket from `allWs`.
147 inline void removeFromAllWs(HibernatableWebSocket& hib);
148 
149 // Handles the termination of the websocket. If termination was not clean, we might try to
150 // dispatch a close event (if we haven't already), or an error event.
151 // We will also remove the HibernatableWebSocket from the HibernationManager's collections.
152 kj::Promise<void> handleSocketTermination(
153 HibernatableWebSocket& hib, kj::Maybe<kj::Exception>& maybeError) KJ_WARN_UNUSED_RESULT;
154 
155 // Like the api::WebSocket readLoop(), but we dispatch different types of events.
156 kj::Promise<void> readLoop(HibernatableWebSocket& hib);
157 
158 // This struct is held by the `tagToWs` hashmap. The key is a StringPtr to tag, and the value
159 // is this struct itself.
160 struct TagCollection {
161 kj::String tag;
162 kj::Own<kj::List<TagListItem, &TagListItem::link>> list;
163 
164 TagCollection(kj::String tag, decltype(list) list): tag(kj::mv(tag)), list(kj::mv(list)) {}
165 TagCollection(TagCollection&& other) = default;
166 };
167 
168 // This structure will hold the request and corresponding response for hibernatable websockets
169 // auto-response feature. Although we store 2 kj::Maybe strings, if we don't have a request set
170 // we can't have a response, and vice versa.
171 // TODO(cleanup): Remove kj::Maybe from request and response strings.
172 struct AutoRequestResponsePair {
173 kj::Maybe<kj::String> request = kj::none;
174 kj::Maybe<kj::String> response = kj::none;
175 };
176 
177 // A hashmap of tags to HibernatableWebSockets associated with the tag.
178 // We use a kj::List so we can quickly remove websockets that have disconnected.
179 // Also note that we box the keys and values such that in the event of a hashmap resizing we don't
180 // move the underlying data (thereby keeping any references intact).
181 kj::HashMap<kj::StringPtr, kj::Own<TagCollection>> tagToWs;
182 
183 // We store all of our HibernatableWebSockets in a doubly linked-list.
184 std::list<kj::Own<HibernatableWebSocket>> allWs;
185 
186 // Used to obtain the worker so we can dispatch Hibernatable websocket events.
187 kj::Own<Worker::Actor::Loopback> loopback;
188 
189 // Passed to HibernatableWebSocket custom event as the typeId.
190 uint16_t hibernationEventType;
191 
192 // A map of { ID -> HibernatableWebSocket } that allows the event handler that is currently
193 // running to access the HibernatableWebSocket that it needs to execute.
194 //
195 // Dispatching events tends to result in races when events are received on different websockets
196 // around the same time. Suppose there are two websockets that disconnect at the same time.
197 // It is possible that both of them will be added to the map (i.e. their `receive()`
198 // will throw) before the first event is dispatched and manages to obtain its associated websocket.
199 kj::HashMap<kj::String, HibernatableWebSocket*> webSocketsForEventHandler;
200 
201 // The maximum number of Hibernatable WebSocket connections a single HibernationManagerImpl
202 // instance can manage.
203 const size_t ACTIVE_CONNECTION_LIMIT = 1024 * 32;
204 
205 class DisconnectHandler: public kj::TaskSet::ErrorHandler {
206 public:
207 // We don't need to do anything here; we already handle disconnects in the callee of readLoop().
208 void taskFailed(kj::Exception&& exception) override {};
209 };
210 DisconnectHandler onDisconnect;
211 kj::TaskSet readLoopTasks;
212 kj::Own<AutoRequestResponsePair> autoResponsePair = kj::heap<AutoRequestResponsePair>();
213 kj::Maybe<TimerChannel&> timer;
214 kj::Maybe<uint32_t> eventTimeoutMs;
215};
216}; // namespace workerd