Skip to content

Commit f804214

Browse files
committed
feat(server): spawn task sooner in listenloop
1 parent 8f35185 commit f804214

File tree

3 files changed

+60
-26
lines changed

3 files changed

+60
-26
lines changed

docs/examples/cors_server.jl

+3-3
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ const CORS_RES_HEADERS = ["Access-Control-Allow-Origin" => "*"]
3939
#=
4040
JSONMiddleware minimizes code by automatically converting the request body
4141
to JSON to pass to the other service functions automatically. JSONMiddleware
42-
recieves the body of the response from the other service funtions and sends
42+
receives the body of the response from the other service funtions and sends
4343
back a success response code
4444
=#
4545
function JSONMiddleware(handler)
@@ -65,9 +65,9 @@ function JSONMiddleware(handler)
6565
end
6666

6767
#= CorsMiddleware: handles preflight request with the OPTIONS flag
68-
If a request was recieved with the correct headers, then a response will be
68+
If a request was received with the correct headers, then a response will be
6969
sent back with a 200 code, if the correct headers were not specified in the request,
70-
then a CORS error will be recieved on the client side
70+
then a CORS error will be received on the client side
7171
7272
Since each request passes throught the CORS Handler, then if the request is
7373
not a preflight request, it will simply go to the JSONMiddleware to be passed to the

src/Servers.jl

+56-22
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ export listen, listen!, Server, forceclose, port
1313

1414
using Sockets, Logging, LoggingExtras, MbedTLS, Dates
1515
using MbedTLS: SSLContext, SSLConfig
16+
using ConcurrentUtilities: Lockable, lock
1617
using ..IOExtras, ..Streams, ..Messages, ..Parsers, ..Connections, ..Exceptions
1718
import ..access_threaded, ..SOCKET_TYPE_TLS, ..@logfmt_str
1819

@@ -83,10 +84,19 @@ accept(s::Listener{SSLConfig}) = getsslcontext(Sockets.accept(s.server), s.ssl)
8384

8485
function getsslcontext(tcp, sslconfig)
8586
try
87+
handshake_done = Ref{Bool}(false)
8688
ssl = MbedTLS.SSLContext()
8789
MbedTLS.setup!(ssl, sslconfig)
8890
MbedTLS.associate!(ssl, tcp)
89-
MbedTLS.handshake!(ssl)
91+
handshake_task = @async begin
92+
MbedTLS.handshake!(ssl)
93+
handshake_done[] = true
94+
end
95+
timedwait(5.0) do
96+
handshake_done[] || istaskdone(handshake_task)
97+
end
98+
!istaskdone(handshake_task) && wait(handshake_task)
99+
handshake_done[] || throw(Base.IOError("SSL handshake timed out", Base.ETIMEDOUT))
90100
return ssl
91101
catch e
92102
@try Base.IOError close(tcp)
@@ -363,31 +373,55 @@ Accepts new tcp connections and spawns async tasks to handle them."
363373
function listenloop(f, listener, conns, tcpisvalid,
364374
max_connections, readtimeout, access_log, ready_to_accept, verbose)
365375
sem = Base.Semaphore(max_connections)
376+
ssl = Lockable(listener.ssl)
377+
connections = Lockable(conns)
366378
verbose >= 0 && @infov 1 "Listening on: $(listener.hostname):$(listener.hostport), thread id: $(Threads.threadid())"
367379
notify(ready_to_accept)
368380
while isopen(listener)
369381
try
370382
Base.acquire(sem)
371-
io = accept(listener)
372-
if io === nothing
373-
@warnv 1 "unable to accept new connection"
374-
continue
375-
elseif !tcpisvalid(io)
376-
@warnv 1 "!tcpisvalid: $io"
377-
close(io)
378-
continue
379-
end
380-
conn = Connection(io)
381-
conn.state = IDLE
382-
push!(conns, conn)
383-
conn.host, conn.port = listener.hostname, listener.hostport
384-
@async try
385-
handle_connection(f, conn, listener, readtimeout, access_log)
386-
finally
387-
# handle_connection is in charge of closing the underlying io
388-
delete!(conns, conn)
389-
Base.release(sem)
390-
end
383+
io = Sockets.accept(listener.server)
384+
Threads.@spawn begin
385+
local conn = nothing
386+
isssl = !isnothing(listener.ssl)
387+
try
388+
if io === nothing
389+
@warnv 1 "unable to accept new connection"
390+
return
391+
end
392+
if isssl
393+
io = lock(ssl) do ssl
394+
return getsslcontext(io, ssl)
395+
end
396+
end
397+
if !tcpisvalid(io)
398+
close(io)
399+
return
400+
end
401+
conn = Connection(io)
402+
conn.state = IDLE
403+
lock(connections) do conns
404+
push!(conns, conn)
405+
end
406+
conn.host, conn.port = listener.hostname, listener.hostport
407+
handle_connection(f, conn, listener, readtimeout, access_log)
408+
catch e
409+
if e isa Base.IOError && e.code == Base.UV_ECONNABORTED
410+
verbose >= 0 && @infov 1 "Server on $(listener.hostname):$(listener.hostport) closing"
411+
else
412+
@errorv 2 "Server on $(listener.hostname):$(listener.hostport) errored" exception=(e, catch_backtrace())
413+
# quick little sleep in case there's a temporary
414+
# local error accepting and this might help avoid quickly re-erroring
415+
sleep(0.05 + rand() * 0.05)
416+
end
417+
# handle_connection is in charge of closing the underlying io, but it may not get there
418+
finally
419+
!isnothing(conn) && lock(connections) do conns
420+
delete!(conns, conn)
421+
end
422+
Base.release(sem)
423+
end
424+
end # Task.@spawn
391425
catch e
392426
if e isa Base.IOError && e.code == Base.UV_ECONNABORTED
393427
verbose >= 0 && @infov 1 "Server on $(listener.hostname):$(listener.hostport) closing"
@@ -442,7 +476,7 @@ function handle_connection(f, c::Connection, listener, readtimeout, access_log)
442476
request.response.status = 200
443477

444478
try
445-
# invokelatest becuase the perf is negligible, but this makes live-editing handlers more Revise friendly
479+
# invokelatest because the perf is negligible, but this makes live-editing handlers more Revise friendly
446480
@debugv 1 "invoking handler"
447481
Base.invokelatest(f, http)
448482
# If `startwrite()` was never called, throw an error so we send a 500 and log this

src/WebSockets.jl

+1-1
Original file line numberDiff line numberDiff line change
@@ -587,7 +587,7 @@ function Base.close(ws::WebSocket, body::CloseFrameBody=CloseFrameBody(1000, "")
587587
ws.readclosed = true
588588
end
589589
end
590-
# we either recieved the responding CLOSE frame and readclosed was set
590+
# we either received the responding CLOSE frame and readclosed was set
591591
# or there was an error/timeout reading it; in any case, readclosed should be closed now
592592
@assert ws.readclosed
593593
# if we're the server, it's our job to close the underlying socket

0 commit comments

Comments
 (0)