Improved the performance of the SCGI module when dealing with multiple

concurrent requests.
This commit is contained in:
Dominik Picheta 2013-05-19 18:33:47 +01:00
commit 0b2512e1e4
4 changed files with 110 additions and 33 deletions

View file

@ -420,6 +420,9 @@ proc isListening*(s: PAsyncSocket): bool =
proc isConnecting*(s: PAsyncSocket): bool = proc isConnecting*(s: PAsyncSocket): bool =
## Determines whether ``s`` is connecting. ## Determines whether ``s`` is connecting.
return s.info == SockConnecting return s.info == SockConnecting
proc isClosed*(s: PAsyncSocket): bool =
## Determines whether ``s`` has been closed.
return s.info == SockClosed
proc setHandleWrite*(s: PAsyncSocket, proc setHandleWrite*(s: PAsyncSocket,
handleWrite: proc (s: PAsyncSocket) {.closure.}) = handleWrite: proc (s: PAsyncSocket) {.closure.}) =

View file

@ -1,7 +1,7 @@
# #
# #
# Nimrod's Runtime Library # Nimrod's Runtime Library
# (c) Copyright 2012 Andreas Rumpf # (c) Copyright 2013 Andreas Rumpf, Dominik Picheta
# #
# See the file "copying.txt", included in this # See the file "copying.txt", included in this
# distribution, for details about the copyright. # distribution, for details about the copyright.
@ -67,10 +67,24 @@ type
headers*: PStringTable ## the parsed headers headers*: PStringTable ## the parsed headers
input*: string ## the input buffer input*: string ## the input buffer
TAsyncScgiState* = object of TScgiState
handleRequest: proc (server: var TAsyncScgiState, client: TSocket, # Async
TClientMode = enum
ClientReadChar, ClientReadHeaders, ClientReadContent
PAsyncClient = ref object
c: PAsyncSocket
mode: TClientMode
dataLen: int
headers: PStringTable ## the parsed headers
input: string ## the input buffer
TAsyncScgiState = object
handleRequest: proc (client: PAsyncSocket,
input: string, headers: PStringTable) {.closure.} input: string, headers: PStringTable) {.closure.}
asyncServer: PAsyncSocket asyncServer: PAsyncSocket
disp: PDispatcher
PAsyncScgiState* = ref TAsyncScgiState PAsyncScgiState* = ref TAsyncScgiState
proc recvBuffer(s: var TScgiState, L: int) = proc recvBuffer(s: var TScgiState, L: int) =
@ -145,37 +159,88 @@ proc run*(handleRequest: proc (client: TSocket, input: string,
s.close() s.close()
# -- AsyncIO start # -- AsyncIO start
proc handleAccept(sock: PAsyncSocket, s: PAsyncScgiState) =
new(s.client) proc recvBufferAsync(client: PAsyncClient, L: int): TReadLineResult =
accept(getSocket(s.asyncServer), s.client) result = ReadPartialLine
var L = 0 var data = ""
if L < 1:
scgiError("Cannot read negative or zero length: " & $L)
let ret = recvAsync(client.c, data, L)
if ret == 0 and data == "":
client.c.close()
return ReadDisconnected
if ret == -1:
return ReadNone # No more data available
client.input.add(data)
if ret == L:
return ReadFullLine
proc handleClientRead(client: PAsyncClient, s: PAsyncScgiState) =
case client.mode
of ClientReadChar:
while true: while true:
var d = s.client.recvChar() var d = ""
if d == '\0': let ret = client.c.recvAsync(d, 1)
if d == "" and ret == 0:
# Disconnected # Disconnected
s.client.close() client.c.close()
return return
if d notin strutils.digits: if ret == -1:
if d != ':': scgiError("':' after length expected") return # No more data available
if d[0] notin strutils.digits:
if d[0] != ':': scgiError("':' after length expected")
break break
L = L * 10 + ord(d) - ord('0') client.dataLen = client.dataLen * 10 + ord(d[0]) - ord('0')
recvBuffer(s[], L+1) client.mode = ClientReadHeaders
s.headers = parseHeaders(s.input, L) handleClientRead(client, s) # Allow progression
if s.headers["SCGI"] != "1": scgiError("SCGI Version 1 expected") of ClientReadHeaders:
L = parseInt(s.headers["CONTENT_LENGTH"]) let ret = recvBufferAsync(client, (client.dataLen+1)-client.input.len)
recvBuffer(s[], L) case ret
of ReadFullLine:
client.headers = parseHeaders(client.input, client.input.len-1)
if client.headers["SCGI"] != "1": scgiError("SCGI Version 1 expected")
client.input = "" # For next part
s.handleRequest(s[], s.client, s.input, s.headers) let contentLen = parseInt(client.headers["CONTENT_LENGTH"])
if contentLen > 0:
client.mode = ClientReadContent
else:
s.handleRequest(client.c, client.input, client.headers)
if not client.c.isClosed: client.c.close()
of ReadPartialLine, ReadDisconnected, ReadNone: return
of ClientReadContent:
let L = parseInt(client.headers["CONTENT_LENGTH"])-client.input.len
if L > 0:
let ret = recvBufferAsync(client, L)
case ret
of ReadFullLine:
s.handleRequest(client.c, client.input, client.headers)
if not client.c.isClosed: client.c.close()
of ReadPartialLine, ReadDisconnected, ReadNone: return
else:
s.handleRequest(client.c, client.input, client.headers)
if not client.c.isClosed: client.c.close()
proc open*(handleRequest: proc (server: var TAsyncScgiState, client: TSocket, proc handleAccept(sock: PAsyncSocket, s: PAsyncScgiState) =
var client: PAsyncSocket
new(client)
accept(s.asyncServer, client)
var asyncClient = PAsyncClient(c: client, mode: ClientReadChar, dataLen: 0,
headers: newStringTable(), input: "")
client.handleRead =
proc (sock: PAsyncSocket) =
handleClientRead(asyncClient, s)
s.disp.register(client)
proc open*(handleRequest: proc (client: PAsyncSocket,
input: string, headers: PStringTable) {.closure.}, input: string, headers: PStringTable) {.closure.},
port = TPort(4000), address = "127.0.0.1"): PAsyncScgiState = port = TPort(4000), address = "127.0.0.1"): PAsyncScgiState =
## Alternative of ``open`` for asyncio compatible SCGI. ## Creates an ``PAsyncScgiState`` object which serves as a SCGI server.
##
## After the execution of ``handleRequest`` the client socket will be closed
## automatically unless it has already been closed.
var cres: PAsyncScgiState var cres: PAsyncScgiState
new(cres) new(cres)
cres.bufLen = 4000
cres.input = newString(cres.buflen) # will be reused
cres.asyncServer = AsyncSocket() cres.asyncServer = AsyncSocket()
cres.asyncServer.handleAccept = proc (s: PAsyncSocket) = handleAccept(s, cres) cres.asyncServer.handleAccept = proc (s: PAsyncSocket) = handleAccept(s, cres)
bindAddr(cres.asyncServer, port, address) bindAddr(cres.asyncServer, port, address)
@ -186,6 +251,7 @@ proc open*(handleRequest: proc (server: var TAsyncScgiState, client: TSocket,
proc register*(d: PDispatcher, s: PAsyncScgiState): PDelegate {.discardable.} = proc register*(d: PDispatcher, s: PAsyncScgiState): PDelegate {.discardable.} =
## Registers ``s`` with dispatcher ``d``. ## Registers ``s`` with dispatcher ``d``.
result = d.register(s.asyncServer) result = d.register(s.asyncServer)
s.disp = d
proc close*(s: PAsyncScgiState) = proc close*(s: PAsyncScgiState) =
## Closes the ``PAsyncScgiState``. ## Closes the ``PAsyncScgiState``.

View file

@ -629,7 +629,8 @@ proc close*(socket: TSocket) =
discard winlean.closeSocket(socket.fd) discard winlean.closeSocket(socket.fd)
else: else:
discard posix.close(socket.fd) discard posix.close(socket.fd)
# TODO: These values should not be discarded. An EOS should be raised.
# http://stackoverflow.com/questions/12463473/what-happens-if-you-call-close-on-a-bsd-socket-multiple-times
when defined(ssl): when defined(ssl):
if socket.isSSL: if socket.isSSL:
discard SSLShutdown(socket.sslHandle) discard SSLShutdown(socket.sslHandle)

View file

@ -19,7 +19,8 @@ Bugfixes
- ``cast`` for floating point types now does the bitcast as specified in the - ``cast`` for floating point types now does the bitcast as specified in the
manual. This breaks code that erroneously uses ``cast`` to convert different manual. This breaks code that erroneously uses ``cast`` to convert different
floating point values. floating point values.
- SCGI module's performance has been improved greatly, it will no longer block
on many concurrent requests.
Library Additions Library Additions
----------------- -----------------
@ -46,9 +47,7 @@ Changes affecting backwards compatibility
this affects very little (if any) real world code. this affects very little (if any) real world code.
- The expression/statement unification has been implemented. Again this - The expression/statement unification has been implemented. Again this
only affects edge cases and no known real world code. only affects edge cases and no known real world code.
- The scope rules of ``if`` statements will change in 0.9.4. This affects the - Changed the async interface of the ``scgi`` module.
``=~`` pegs/re templates.
Compiler Additions Compiler Additions
------------------ ------------------
@ -88,6 +87,14 @@ Language Additions
- There is a new syntactic construct ``(;)`` unifying expressions and - There is a new syntactic construct ``(;)`` unifying expressions and
statements. statements.
Notes for the future
--------------------
- The scope rules of ``if`` statements will change in 0.9.4. This affects the
``=~`` pegs/re templates.
- The ``sockets`` module will become a low-level wrapper of OS-specific socket
functions. All the high-level features of the current ``sockets`` module
will be moved to a ``network`` module.
2012-09-23 Version 0.9.0 released 2012-09-23 Version 0.9.0 released
================================= =================================