Skip to content

Commit f733e49

Browse files
authored
Refactor code a bit to reduce allocations and dynamic dispatches (#10)
* Refactor code a bit to reduce allocations and dynamic dispatches Together with JuliaWeb/HTTP.jl#985, this reduces allocations on a typical request by about ~100. The main wins here are hard-coding TCPSocket as the `io` field of `SSLStream` (which we assume anyway), and changing the `geterror` function to a macro to avoid the closure boxing problem. * Run HTTP.jl tests on changes in OpenSSL * fix
1 parent 6789aa0 commit f733e49

File tree

2 files changed

+104
-53
lines changed

2 files changed

+104
-53
lines changed

.github/workflows/IntegrationTest.yml

+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
name: IntegrationTest
2+
on:
3+
push:
4+
branches: [main]
5+
tags: [v*]
6+
pull_request:
7+
8+
concurrency:
9+
# Skip intermediate builds: always.
10+
# Cancel intermediate builds: only if it is a pull request build.
11+
group: ${{ github.workflow }}-${{ github.ref }}
12+
cancel-in-progress: ${{ startsWith(github.ref, 'refs/pull/') }}
13+
14+
jobs:
15+
test:
16+
name: ${{ matrix.package.repo }}
17+
runs-on: ${{ matrix.os }}
18+
strategy:
19+
fail-fast: false
20+
matrix:
21+
julia-version: [1]
22+
os: [ubuntu-latest]
23+
package:
24+
- {user: JuliaWeb, repo: HTTP.jl}
25+
26+
steps:
27+
- uses: actions/checkout@v2
28+
- uses: julia-actions/setup-julia@v1
29+
with:
30+
version: ${{ matrix.julia-version }}
31+
arch: x64
32+
- uses: julia-actions/julia-buildpkg@latest
33+
- name: Clone Downstream
34+
uses: actions/checkout@v2
35+
with:
36+
repository: ${{ matrix.package.user }}/${{ matrix.package.repo }}
37+
path: downstream
38+
- name: Load this and run the downstream tests
39+
shell: julia --project=downstream {0}
40+
run: |
41+
using Pkg
42+
try
43+
# Force downstream tests to use this PR's version of the package.
44+
Pkg.develop(PackageSpec(path=".")) # resolver may fail with main deps
45+
Pkg.update()
46+
Pkg.test() # resolver may fail with test time deps
47+
catch err
48+
err isa Pkg.Resolve.ResolverError || rethrow()
49+
# If we can't resolve that means this is incompatible by SemVer and this is fine;
50+
# it means we marked this as a breaking change, so we don't need to worry about
51+
# mistakenly introducing a breaking change, as we have intentionally made one
52+
@info "Not compatible with this release. No problem." exception=err
53+
exit(0) # Exit immediately as a success.
54+
end

src/ssl.jl

+50-53
Original file line numberDiff line numberDiff line change
@@ -397,7 +397,7 @@ mutable struct SSLStream <: IO
397397
ssl_context::SSLContext
398398
rbio::BIO
399399
wbio::BIO
400-
io::IO
400+
io::TCPSocket
401401
lock::ReentrantLock
402402
@static if VERSION < v"1.7"
403403
close_notify_received::Threads.Atomic{Bool}
@@ -407,7 +407,7 @@ else
407407
@atomic closed::Bool
408408
end
409409

410-
function SSLStream(ssl_context::SSLContext, io::IO)
410+
function SSLStream(ssl_context::SSLContext, io::TCPSocket)
411411
# Create a read and write BIOs.
412412
bio_read::BIO = BIO(io; finalize=false)
413413
bio_write::BIO = BIO(io; finalize=false)
@@ -422,7 +422,7 @@ end
422422
end
423423

424424
# backwards compat
425-
SSLStream(ssl_context::SSLContext, io::IO, ::IO) = SSLStream(ssl_context, io)
425+
SSLStream(ssl_context::SSLContext, io::TCPSocket, ::TCPSocket) = SSLStream(ssl_context, io)
426426
Base.getproperty(ssl::SSLStream, nm::Symbol) = nm === :bio_read_stream ? ssl : getfield(ssl, nm)
427427

428428
SSLStream(tcp::TCPSocket) = SSLStream(SSLContext(OpenSSL.TLSClientMethod()), tcp)
@@ -432,45 +432,44 @@ Base.isopen(ssl::SSLStream)::Bool = !@atomicget(ssl.closed)
432432
Base.iswritable(ssl::SSLStream)::Bool = isopen(ssl) && isopen(ssl.io)
433433
check_isopen(ssl::SSLStream, op) = isopen(ssl) || throw(Base.IOError("$op requires ssl to be open", 0))
434434

435-
function geterror(f, ssl::SSLStream)
436-
ret = f()
437-
if ret <= 0
438-
err = get_error(ssl.ssl, ret)
439-
if err == SSL_ERROR_ZERO_RETURN
440-
@atomicset ssl.close_notify_received = true
441-
elseif err == SSL_ERROR_NONE
442-
# pass
443-
elseif err == SSL_ERROR_WANT_READ
444-
return SSL_ERROR_WANT_READ
445-
elseif err == SSL_ERROR_WANT_WRITE
446-
return SSL_ERROR_WANT_WRITE
447-
else
448-
close(ssl, false)
449-
throw(Base.IOError(OpenSSLError(err).msg, 0))
435+
macro geterror(expr)
436+
esc(quote
437+
ret = $expr
438+
if ret <= 0
439+
err = get_error(ssl.ssl, ret)
440+
if err == SSL_ERROR_ZERO_RETURN
441+
@atomicset ssl.close_notify_received = true
442+
elseif err == SSL_ERROR_NONE
443+
# pass
444+
elseif err == SSL_ERROR_WANT_READ
445+
ret = SSL_ERROR_WANT_READ
446+
elseif err == SSL_ERROR_WANT_WRITE
447+
ret = SSL_ERROR_WANT_WRITE
448+
else
449+
close(ssl, false)
450+
throw(Base.IOError(OpenSSLError(err).msg, 0))
451+
end
450452
end
451-
end
452-
return ret
453+
end)
453454
end
454455

455456
function Base.unsafe_write(ssl::SSLStream, in_buffer::Ptr{UInt8}, in_length::UInt)
456457
check_isopen(ssl, "unsafe_write")
457-
return geterror(ssl) do
458-
ccall(
459-
(:SSL_write, libssl),
460-
Cint,
461-
(SSL, Ptr{Cvoid}, Cint),
462-
ssl.ssl,
463-
in_buffer,
464-
in_length)
465-
end
458+
@geterror ccall(
459+
(:SSL_write, libssl),
460+
Cint,
461+
(SSL, Ptr{Cvoid}, Cint),
462+
ssl.ssl,
463+
in_buffer,
464+
in_length
465+
)
466+
return ret
466467
end
467468

468469
function Sockets.connect(ssl::SSLStream; require_ssl_verification::Bool=true)
469470
while true
470471
check_isopen(ssl, "connect")
471-
ret = geterror(ssl) do
472-
ssl_connect(ssl.ssl)
473-
end
472+
@geterror ssl_connect(ssl.ssl)
474473
if (ret == 1 || ret == SSL_ERROR_NONE)
475474
break
476475
elseif ret == SSL_ERROR_WANT_READ
@@ -537,17 +536,16 @@ function Base.unsafe_read(ssl::SSLStream, buf::Ptr{UInt8}, nbytes::UInt)
537536
while nread < nbytes
538537
(!isopen(ssl) || eof(ssl)) && throw(EOFError())
539538
readbytes = Ref{Csize_t}()
540-
geterror(ssl) do
541-
ccall(
542-
(:SSL_read_ex, libssl),
543-
Cint,
544-
(SSL, Ptr{Int8}, Csize_t, Ptr{Csize_t}),
545-
ssl.ssl,
546-
buf + nread,
547-
nbytes - nread,
548-
readbytes)
549-
end
550-
nread += readbytes[]
539+
@geterror ccall(
540+
(:SSL_read_ex, libssl),
541+
Cint,
542+
(SSL, Ptr{Int8}, Csize_t, Ptr{Csize_t}),
543+
ssl.ssl,
544+
buf + nread,
545+
nbytes - nread,
546+
readbytes
547+
)
548+
nread += Int(readbytes[])
551549
end
552550
return nread
553551
end
@@ -600,19 +598,18 @@ function Base.eof(ssl::SSLStream)::Bool
600598
end
601599
# if we're here, we know there are unprocessed bytes,
602600
# so we call peek to force processing
603-
err = geterror(ssl) do
604-
ccall(
605-
(:SSL_peek, libssl),
606-
Cint,
607-
(SSL, Ref{UInt8}, Cint),
608-
ssl.ssl,
609-
PEEK_REF,
610-
1)
611-
end
601+
@geterror ccall(
602+
(:SSL_peek, libssl),
603+
Cint,
604+
(SSL, Ref{UInt8}, Cint),
605+
ssl.ssl,
606+
PEEK_REF,
607+
1
608+
)
612609
# if we get WANT_READ back, that means there were pending bytes
613610
# to be processed, but not a full record, so we need to wait
614611
# for additional bytes to come in before we can process
615-
err == SSL_ERROR_WANT_READ && eof(ssl.io)
612+
ret == SSL_ERROR_WANT_READ && eof(ssl.io)
616613
end
617614
end
618615
bytesavailable(ssl) > 0 && return false

0 commit comments

Comments
 (0)