Skip to content

Commit 8b4a9e5

Browse files
committed
lazily allocate the threaded buffers and allocate them on the thread that will access it
1 parent 255a759 commit 8b4a9e5

File tree

5 files changed

+69
-38
lines changed

5 files changed

+69
-38
lines changed

src/CookieRequest.jl

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,12 @@ import ..Layer, ..request
55
using URIs
66
using ..Cookies
77
using ..Pairs: getkv, setkv
8-
import ..@debug, ..DEBUG_LEVEL
8+
import ..@debug, ..DEBUG_LEVEL, ..access_threaded
99

10-
const default_cookiejar = [Dict{String, Set{Cookie}}()]
10+
const default_cookiejar = Dict{String, Set{Cookie}}[]
1111

1212
function __init__()
13-
Threads.resize_nthreads!(default_cookiejar)
13+
resize!(empty!(default_cookiejar), Threads.nthreads())
1414
return
1515
end
1616

@@ -26,7 +26,7 @@ export CookieLayer
2626
function request(::Type{CookieLayer{Next}},
2727
method::String, url::URI, headers, body;
2828
cookies::Union{Bool, Dict{<:AbstractString, <:AbstractString}}=Dict{String, String}(),
29-
cookiejar::Dict{String, Set{Cookie}}=default_cookiejar[Threads.threadid()],
29+
cookiejar::Dict{String, Set{Cookie}}=access_threaded(Dict{String, Set{Cookie}}, default_cookiejar),
3030
kw...) where {Next}
3131

3232
hostcookies = get!(cookiejar, url.host, Set{Cookie}())

src/HTTP.jl

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,19 @@ Base.@deprecate escape escapeuri
1212
using Base64, Sockets, Dates
1313
using URIs
1414

15+
function access_threaded(f, v::Vector)
16+
tid = Threads.threadid()
17+
0 < tid <= length(v) || _length_assert()
18+
if @inbounds isassigned(v, tid)
19+
@inbounds x = v[tid]
20+
else
21+
x = f()
22+
@inbounds v[tid] = x
23+
end
24+
return x
25+
end
26+
@noinline _length_assert() = @assert false "0 < tid <= v"
27+
1528
include("debug.jl")
1629

1730
include("Pairs.jl") ;using .Pairs

src/Parsers.jl

Lines changed: 46 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ have field names compatible with those expected by the `parse_status_line!` and
1717
"""
1818
module Parsers
1919

20+
import ..access_threaded
21+
2022
export Header, Headers,
2123
find_end_of_header, find_end_of_chunk_size, find_end_of_trailer,
2224
parse_status_line!, parse_request_line!, parse_header_field,
@@ -49,17 +51,24 @@ ParseError(code::Symbol, bytes="") =
4951

5052
# Regular expressions for parsing HTTP start-line and header-fields
5153

54+
init!(r::RegexAndMatchData) = (Base.compile(r.re); initialize!(r); r)
55+
5256
"""
5357
https://tools.ietf.org/html/rfc7230#section-3.1.1
5458
request-line = method SP request-target SP HTTP-version CRLF
5559
"""
56-
const request_line_regex = [RegexAndMatchData(r"""^
60+
const request_line_regex = RegexAndMatchData[]
61+
function request_line_regex_f()
62+
r = RegexAndMatchData(r"""^
5763
(?: \r? \n) ? # ignore leading blank line
5864
([!#$%&'*+\-.^_`|~[:alnum:]]+) [ ]+ # 1. method = token (RFC7230 3.2.6)
5965
([^.][^ \r\n]*) [ ]+ # 2. target
6066
HTTP/(\d\.\d) # 3. version
6167
\r? \n # CRLF
62-
"""x)]
68+
"""x)
69+
init!(r)
70+
end
71+
6372

6473
"""
6574
https://tools.ietf.org/html/rfc7230#section-3.1.2
@@ -68,40 +77,58 @@ status-line = HTTP-version SP status-code SP reason-phrase CRLF
6877
See:
6978
[#190](https://github.com/JuliaWeb/HTTP.jl/issues/190#issuecomment-363314009)
7079
"""
71-
const status_line_regex = [RegexAndMatchData(r"""^
80+
const status_line_regex = RegexAndMatchData[]
81+
function status_line_regex_f()
82+
r = RegexAndMatchData(r"""^
7283
[ ]? # Issue #190
7384
HTTP/(\d\.\d) [ ]+ # 1. version
7485
(\d\d\d) .* # 2. status
7586
\r? \n # CRLF
76-
"""x)]
87+
"""x)
88+
init!(r)
89+
end
7790

7891
"""
7992
https://tools.ietf.org/html/rfc7230#section-3.2
8093
header-field = field-name ":" OWS field-value OWS
8194
"""
82-
const header_field_regex = [RegexAndMatchData(r"""^
95+
const header_field_regex = RegexAndMatchData[]
96+
function header_field_regex_f()
97+
r = RegexAndMatchData(r"""^
8398
([!#$%&'*+\-.^_`|~[:alnum:]]+) : # 1. field-name = token (RFC7230 3.2.6)
8499
[ \t]* # OWS
85100
([^\r\n]*?) # 2. field-value
86101
[ \t]* # OWS
87102
\r? \n # CRLF
88103
(?= [^ \t]) # no WS on next line
89-
"""x)]
104+
"""x)
105+
init!(r)
106+
end
107+
90108

91109
"""
92110
https://tools.ietf.org/html/rfc7230#section-3.2.4
93111
obs-fold = CRLF 1*( SP / HTAB )
94112
"""
95-
const obs_fold_header_field_regex = [RegexAndMatchData(r"""^
113+
const obs_fold_header_field_regex = RegexAndMatchData[]
114+
function obs_fold_header_field_regex_f()
115+
r = RegexAndMatchData(r"""^
96116
([!#$%&'*+\-.^_`|~[:alnum:]]+) : # 1. field-name = token (RFC7230 3.2.6)
97117
[ \t]* # OWS
98118
([^\r\n]* # 2. field-value
99119
(?: \r? \n [ \t] [^\r\n]*)*) # obs-fold
100120
[ \t]* # OWS
101121
\r? \n # CRLF
102-
"""x)]
122+
"""x)
123+
init!(r)
124+
end
125+
126+
const empty_header_field_regex = RegexAndMatchData[]
127+
function empty_header_field_regex_f()
128+
r = RegexAndMatchData(r"^ \r? \n"x)
129+
init!(r)
130+
end
103131

104-
const empty_header_field_regex = [RegexAndMatchData(r"^ \r? \n"x)]
105132

106133
# HTTP start-line and header-field parsing
107134

@@ -157,7 +184,7 @@ Parse HTTP request-line `bytes` and set the
157184
Return a `SubString` containing the header-field lines.
158185
"""
159186
function parse_request_line!(bytes::AbstractString, request)::SubString{String}
160-
re = request_line_regex[Threads.threadid()]
187+
re = access_threaded(request_line_regex_f, request_line_regex)
161188
if !exec(re, bytes)
162189
throw(ParseError(:INVALID_REQUEST_LINE, bytes))
163190
end
@@ -173,7 +200,7 @@ Parse HTTP response-line `bytes` and set the
173200
Return a `SubString` containing the header-field lines.
174201
"""
175202
function parse_status_line!(bytes::AbstractString, response)::SubString{String}
176-
re = status_line_regex[Threads.threadid()]
203+
re = access_threaded(status_line_regex_f, status_line_regex)
177204
if !exec(re, bytes)
178205
throw(ParseError(:INVALID_STATUS_LINE, bytes))
179206
end
@@ -189,20 +216,20 @@ a `SubString` containing the remaining header-field lines.
189216
"""
190217
function parse_header_field(bytes::SubString{String})::Tuple{Header,SubString{String}}
191218
# First look for: field-name ":" field-value
192-
re = header_field_regex[Threads.threadid()]
219+
re = access_threaded(header_field_regex_f, header_field_regex)
193220
if exec(re, bytes)
194221
return (group(1, re, bytes) => group(2, re, bytes)),
195222
nextbytes(re, bytes)
196223
end
197224

198225
# Then check for empty termination line:
199-
re = empty_header_field_regex[Threads.threadid()]
226+
re = access_threaded(empty_header_field_regex_f, empty_header_field_regex)
200227
if exec(re, bytes)
201228
return emptyheader, nextbytes(re, bytes)
202229
end
203230

204231
# Finally look for obsolete line folding format:
205-
re = obs_fold_header_field_regex[Threads.threadid()]
232+
re = access_threaded(obs_fold_header_field_regex_f, obs_fold_header_field_regex)
206233
if exec(re, bytes)
207234
unfold = SubString(replace(group(2, re, bytes), r"\r?\n"=>""))
208235
return (group(1, re, bytes) => unfold), nextbytes(re, bytes)
@@ -312,21 +339,11 @@ const unhex = Int8[
312339
function __init__()
313340
# FIXME Consider turing off `PCRE.UTF` in `Regex.compile_options`
314341
# https://github.com/JuliaLang/julia/pull/26731#issuecomment-380676770
315-
Threads.resize_nthreads!(status_line_regex)
316-
Threads.resize_nthreads!(request_line_regex)
317-
Threads.resize_nthreads!(header_field_regex)
318-
Threads.resize_nthreads!(obs_fold_header_field_regex)
319-
Threads.resize_nthreads!(empty_header_field_regex)
320-
foreach(x -> Base.compile(x.re), status_line_regex)
321-
foreach(x -> Base.compile(x.re), request_line_regex)
322-
foreach(x -> Base.compile(x.re), header_field_regex)
323-
foreach(x -> Base.compile(x.re), empty_header_field_regex)
324-
foreach(x -> Base.compile(x.re), obs_fold_header_field_regex)
325-
foreach(initialize!, status_line_regex)
326-
foreach(initialize!, request_line_regex)
327-
foreach(initialize!, header_field_regex)
328-
foreach(initialize!, empty_header_field_regex)
329-
foreach(initialize!, obs_fold_header_field_regex)
342+
resize!(empty!(status_line_regex), Threads.nthreads())
343+
resize!(empty!(request_line_regex), Threads.nthreads())
344+
resize!(empty!(header_field_regex), Threads.nthreads())
345+
resize!(empty!(obs_fold_header_field_regex), Threads.nthreads())
346+
resize!(empty!(empty_header_field_regex), Threads.nthreads())
330347
return
331348
end
332349

src/Servers.jl

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import MbedTLS
2323

2424
using Dates
2525

26-
import ..@debug, ..@debugshow, ..DEBUG_LEVEL, ..taskid
26+
import ..@debug, ..@debugshow, ..DEBUG_LEVEL, ..taskid, ..access_threaded
2727

2828
# rate limiting
2929
mutable struct RateLimit
@@ -42,7 +42,7 @@ function update!(rl::RateLimit, rate_limit)
4242
return nothing
4343
end
4444

45-
const RATE_LIMITS = [Dict{IPAddr, RateLimit}()]
45+
const RATE_LIMITS = Dict{IPAddr, RateLimit}[]
4646
check_rate_limit(tcp::Base.PipeEndpoint, rate_limit::Rational{Int}) = true
4747
check_rate_limit(tcp, ::Nothing) = true
4848

@@ -55,7 +55,8 @@ ip address is updated in the global cache.
5555
"""
5656
function check_rate_limit(tcp, rate_limit::Rational{Int})
5757
ip = Sockets.getpeername(tcp)[1]
58-
rl = get!(RATE_LIMITS[Threads.threadid()], ip, RateLimit(rate_limit, Dates.DateTime(0)))
58+
rl_d = access_threaded(Dict{IPAddr, RateLimit}, RATE_LIMITS)
59+
rl = get!(rl_d, ip, RateLimit(rate_limit, Dates.DateTime(0)))
5960
update!(rl, rate_limit)
6061
if rl.allowance < 1.0
6162
@warn "discarding connection from $ip due to rate limiting"
@@ -427,7 +428,7 @@ function handle_transaction(f, t::Transaction, server; final_transaction::Bool=f
427428
end
428429

429430
function __init__()
430-
Threads.resize_nthreads!(RATE_LIMITS)
431+
resize!(empty!(RATE_LIMITS), Threads.nthreads())
431432
return
432433
end
433434

test/client.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ end
3737
end
3838

3939
@testset "Cookie Requests" begin
40-
empty!(HTTP.CookieRequest.default_cookiejar[1])
40+
empty!(HTTP.access_threaded(Dict{String, Set{HTTP.Cookie}}, HTTP.CookieRequest.default_cookiejar))
4141
r = HTTP.get("$sch://httpbin.org/cookies", cookies=true)
4242

4343
body = String(r.body)

0 commit comments

Comments
 (0)