@@ -122,72 +122,116 @@ class BindingData : public BaseObject { |
122 | 122 | SET_MEMORY_INFO_NAME(BindingData) |
123 | 123 | }; |
124 | 124 |
|
125 | | -// helper class for the Parser |
126 | | -struct StringPtr { |
127 | | - StringPtr() { |
128 | | - on_heap_ = false; |
129 | | - Reset(); |
130 | | - } |
| 125 | +class Parser; |
| 126 | + |
| 127 | +class StringPtrAllocator { |
| 128 | + public: |
| 129 | + // Memory impact: ~8KB per parser (66 StringPtr × 128 bytes). |
| 130 | + static constexpr size_t kSlabSize = 8192; |
131 | 131 |
|
| 132 | + StringPtrAllocator() = default; |
132 | 133 |
|
133 | | - ~StringPtr() { |
134 | | - Reset(); |
| 134 | + // Allocate memory from the slab. Returns nullptr if full. |
| 135 | + char* TryAllocate(size_t size) { |
| 136 | + if (length_ + size > kSlabSize) { |
| 137 | + return nullptr; |
| 138 | + } |
| 139 | + char* ptr = buffer_ + length_; |
| 140 | + length_ += size; |
| 141 | + return ptr; |
| 142 | + } |
| 143 | + |
| 144 | + // Check if pointer is within this allocator's buffer. |
| 145 | + bool Contains(const char* ptr) const { |
| 146 | + return ptr >= buffer_ && ptr < buffer_ + kSlabSize; |
135 | 147 | } |
136 | 148 |
|
| 149 | + // Reset allocator for new message. |
| 150 | + void Reset() { length_ = 0; } |
137 | 151 |
|
138 | | - // If str_ does not point to a heap string yet, this function makes it do |
| 152 | + private: |
| 153 | + char buffer_[kSlabSize]; |
| 154 | + size_t length_ = 0; |
| 155 | +}; |
| 156 | + |
| 157 | +struct StringPtr { |
| 158 | + StringPtr() = default; |
| 159 | + ~StringPtr() { Reset(); } |
| 160 | + |
| 161 | + StringPtr(const StringPtr&) = delete; |
| 162 | + StringPtr& operator=(const StringPtr&) = delete; |
| 163 | + |
| 164 | + // If str_ does not point to owned storage yet, this function makes it do |
139 | 165 | // so. This is called at the end of each http_parser_execute() so as not |
140 | 166 | // to leak references. See issue #2438 and test-http-parser-bad-ref.js. |
141 | | - void Save() { |
142 | | - if (!on_heap_ && size_ > 0) { |
143 | | - char* s = new char[size_]; |
144 | | - memcpy(s, str_, size_); |
145 | | - str_ = s; |
146 | | - on_heap_ = true; |
| 167 | + void Save(StringPtrAllocator* allocator) { |
| 168 | + if (str_ == nullptr || on_heap_ || |
| 169 | + (allocator != nullptr && allocator->Contains(str_))) { |
| 170 | + return; |
147 | 171 | } |
| 172 | + // Try allocator first, fall back to heap |
| 173 | + if (allocator != nullptr) { |
| 174 | + char* ptr = allocator->TryAllocate(size_); |
| 175 | + if (ptr != nullptr) { |
| 176 | + memcpy(ptr, str_, size_); |
| 177 | + str_ = ptr; |
| 178 | + return; |
| 179 | + } |
| 180 | + } |
| 181 | + char* s = new char[size_]; |
| 182 | + memcpy(s, str_, size_); |
| 183 | + str_ = s; |
| 184 | + on_heap_ = true; |
148 | 185 | } |
149 | 186 |
|
150 | | - |
151 | 187 | void Reset() { |
152 | 188 | if (on_heap_) { |
153 | 189 | delete[] str_; |
154 | 190 | on_heap_ = false; |
155 | 191 | } |
156 | | - |
157 | 192 | str_ = nullptr; |
158 | 193 | size_ = 0; |
159 | 194 | } |
160 | 195 |
|
161 | | - |
162 | | - void Update(const char* str, size_t size) { |
| 196 | + void Update(const char* str, size_t size, StringPtrAllocator* allocator) { |
163 | 197 | if (str_ == nullptr) { |
164 | 198 | str_ = str; |
165 | | - } else if (on_heap_ || str_ + size_ != str) { |
166 | | - // Non-consecutive input, make a copy on the heap. |
167 | | - // TODO(bnoordhuis) Use slab allocation, O(n) allocs is bad. |
168 | | - char* s = new char[size_ + size]; |
169 | | - memcpy(s, str_, size_); |
170 | | - memcpy(s + size_, str, size); |
171 | | - |
172 | | - if (on_heap_) |
173 | | - delete[] str_; |
174 | | - else |
175 | | - on_heap_ = true; |
| 199 | + } else if (on_heap_ || |
| 200 | + (allocator != nullptr && allocator->Contains(str_)) || |
| 201 | + str_ + size_ != str) { |
| 202 | + // Non-consecutive input, make a copy |
| 203 | + const size_t new_size = size_ + size; |
| 204 | + char* new_str = nullptr; |
| 205 | + |
| 206 | + // Try allocator first (if not already on heap) |
| 207 | + if (!on_heap_ && allocator != nullptr) { |
| 208 | + new_str = allocator->TryAllocate(new_size); |
| 209 | + } |
176 | 210 |
|
177 | | - str_ = s; |
| 211 | + if (new_str != nullptr) { |
| 212 | + memcpy(new_str, str_, size_); |
| 213 | + memcpy(new_str + size_, str, size); |
| 214 | + str_ = new_str; |
| 215 | + } else { |
| 216 | + // Fall back to heap |
| 217 | + char* s = new char[new_size]; |
| 218 | + memcpy(s, str_, size_); |
| 219 | + memcpy(s + size_, str, size); |
| 220 | + if (on_heap_) delete[] str_; |
| 221 | + str_ = s; |
| 222 | + on_heap_ = true; |
| 223 | + } |
178 | 224 | } |
179 | 225 | size_ += size; |
180 | 226 | } |
181 | 227 |
|
182 | | - |
183 | 228 | Local<String> ToString(Environment* env) const { |
184 | 229 | if (size_ != 0) |
185 | 230 | return OneByteString(env->isolate(), str_, size_); |
186 | 231 | else |
187 | 232 | return String::Empty(env->isolate()); |
188 | 233 | } |
189 | 234 |
|
190 | | - |
191 | 235 | // Strip trailing OWS (SPC or HTAB) from string. |
192 | 236 | Local<String> ToTrimmedString(Environment* env) { |
193 | 237 | while (size_ > 0 && IsOWS(str_[size_ - 1])) { |
@@ -196,14 +240,11 @@ struct StringPtr { |
196 | 240 | return ToString(env); |
197 | 241 | } |
198 | 242 |
|
199 | | - |
200 | | - const char* str_; |
201 | | - bool on_heap_; |
202 | | - size_t size_; |
| 243 | + const char* str_ = nullptr; |
| 244 | + bool on_heap_ = false; |
| 245 | + size_t size_ = 0; |
203 | 246 | }; |
204 | 247 |
|
205 | | -class Parser; |
206 | | - |
207 | 248 | struct ParserComparator { |
208 | 249 | bool operator()(const Parser* lhs, const Parser* rhs) const; |
209 | 250 | }; |
@@ -259,8 +300,7 @@ class Parser : public AsyncWrap, public StreamListener { |
259 | 300 | : AsyncWrap(binding_data->env(), wrap), |
260 | 301 | current_buffer_len_(0), |
261 | 302 | current_buffer_data_(nullptr), |
262 | | - binding_data_(binding_data) { |
263 | | - } |
| 303 | + binding_data_(binding_data) {} |
264 | 304 |
|
265 | 305 | SET_NO_MEMORY_INFO() |
266 | 306 | SET_MEMORY_INFO_NAME(Parser) |
@@ -278,6 +318,7 @@ class Parser : public AsyncWrap, public StreamListener { |
278 | 318 | headers_completed_ = false; |
279 | 319 | chunk_extensions_nread_ = 0; |
280 | 320 | last_message_start_ = uv_hrtime(); |
| 321 | + allocator_.Reset(); |
281 | 322 | url_.Reset(); |
282 | 323 | status_message_.Reset(); |
283 | 324 |
|
@@ -308,7 +349,7 @@ class Parser : public AsyncWrap, public StreamListener { |
308 | 349 | return rv; |
309 | 350 | } |
310 | 351 |
|
311 | | - url_.Update(at, length); |
| 352 | + url_.Update(at, length, &allocator_); |
312 | 353 | return 0; |
313 | 354 | } |
314 | 355 |
|
@@ -319,7 +360,7 @@ class Parser : public AsyncWrap, public StreamListener { |
319 | 360 | return rv; |
320 | 361 | } |
321 | 362 |
|
322 | | - status_message_.Update(at, length); |
| 363 | + status_message_.Update(at, length, &allocator_); |
323 | 364 | return 0; |
324 | 365 | } |
325 | 366 |
|
@@ -345,7 +386,7 @@ class Parser : public AsyncWrap, public StreamListener { |
345 | 386 | CHECK_LT(num_fields_, kMaxHeaderFieldsCount); |
346 | 387 | CHECK_EQ(num_fields_, num_values_ + 1); |
347 | 388 |
|
348 | | - fields_[num_fields_ - 1].Update(at, length); |
| 389 | + fields_[num_fields_ - 1].Update(at, length, &allocator_); |
349 | 390 |
|
350 | 391 | return 0; |
351 | 392 | } |
@@ -366,7 +407,7 @@ class Parser : public AsyncWrap, public StreamListener { |
366 | 407 | CHECK_LT(num_values_, arraysize(values_)); |
367 | 408 | CHECK_EQ(num_values_, num_fields_); |
368 | 409 |
|
369 | | - values_[num_values_ - 1].Update(at, length); |
| 410 | + values_[num_values_ - 1].Update(at, length, &allocator_); |
370 | 411 |
|
371 | 412 | return 0; |
372 | 413 | } |
@@ -594,15 +635,15 @@ class Parser : public AsyncWrap, public StreamListener { |
594 | 635 | } |
595 | 636 |
|
596 | 637 | void Save() { |
597 | | - url_.Save(); |
598 | | - status_message_.Save(); |
| 638 | + url_.Save(&allocator_); |
| 639 | + status_message_.Save(&allocator_); |
599 | 640 |
|
600 | 641 | for (size_t i = 0; i < num_fields_; i++) { |
601 | | - fields_[i].Save(); |
| 642 | + fields_[i].Save(&allocator_); |
602 | 643 | } |
603 | 644 |
|
604 | 645 | for (size_t i = 0; i < num_values_; i++) { |
605 | | - values_[i].Save(); |
| 646 | + values_[i].Save(&allocator_); |
606 | 647 | } |
607 | 648 | } |
608 | 649 |
|
@@ -1006,6 +1047,7 @@ class Parser : public AsyncWrap, public StreamListener { |
1006 | 1047 |
|
1007 | 1048 |
|
1008 | 1049 | llhttp_t parser_; |
| 1050 | + StringPtrAllocator allocator_; // shared slab for all StringPtrs |
1009 | 1051 | StringPtr fields_[kMaxHeaderFieldsCount]; // header fields |
1010 | 1052 | StringPtr values_[kMaxHeaderFieldsCount]; // header values |
1011 | 1053 | StringPtr url_; |
|