Handle overload spikes gracefully
This commit is contained in:
1 parent
f0b9c32ab4
commit
b2c5f5ecc9
2 files changed
+46
-7
No files matched your search
@@ -64,6 +64,7 @@ options = {
|
|||||||
COOKIES_PATH : {type : 'string'},
|
COOKIES_PATH : {type : 'string'},
|
||||||
LOG_FORMAT : {type : 'string', default : 'simple'},
|
LOG_FORMAT : {type : 'string', default : 'simple'},
|
||||||
ACCESS_LOG_FORMAT : {type : 'string', default : ''},
|
ACCESS_LOG_FORMAT : {type : 'string', default : ''},
|
||||||
|
MAX_CONCURRENT_REQUESTS : {type : 'int', default: 20},
|
||||||
|
|
||||||
# XML configuration
|
# XML configuration
|
||||||
XML_RECOVER : {type : 'bool', default : false},
|
XML_RECOVER : {type : 'bool', default : false},
|
||||||
|
|||||||
+45
-7
@@ -34,12 +34,20 @@ logger = require './logger'
|
|||||||
|
|
||||||
MAX_CYCLES = 4
|
MAX_CYCLES = 4
|
||||||
|
|
||||||
|
MAX_CONCURRENT_REQUESTS = config.get('MAX_CONCURRENT_REQUESTS')
|
||||||
|
stats = {
|
||||||
|
concurrentRequests: 0
|
||||||
|
totalRequests: 0
|
||||||
|
droppedRequests: 0
|
||||||
|
initiatedSessions: 0
|
||||||
|
}
|
||||||
|
|
||||||
throwError = (err, httpResponse) ->
|
throwError = (err, httpResponse) ->
|
||||||
if httpResponse
|
if httpResponse
|
||||||
currentSessions.delete(httpResponse.connection)
|
currentSessions.delete(httpResponse.connection)
|
||||||
httpResponse.writeHead(500, {'Connection' : 'close'})
|
httpResponse.writeHead(500, {'Connection' : 'close'})
|
||||||
httpResponse.end("#{err.name}: #{err.message}")
|
httpResponse.end("#{err.name}: #{err.message}")
|
||||||
|
stats.concurrentRequests -= 1
|
||||||
|
|
||||||
throw err
|
throw err
|
||||||
|
|
||||||
@@ -69,6 +77,7 @@ writeResponse = (sessionContext, res) ->
|
|||||||
sessionContext.httpResponse.end(data)
|
sessionContext.httpResponse.end(data)
|
||||||
delete sessionContext.httpRequest
|
delete sessionContext.httpRequest
|
||||||
delete sessionContext.httpResponse
|
delete sessionContext.httpResponse
|
||||||
|
stats.concurrentRequests -= 1
|
||||||
)
|
)
|
||||||
else
|
else
|
||||||
res.headers['Content-Length'] = res.data.length
|
res.headers['Content-Length'] = res.data.length
|
||||||
@@ -76,6 +85,7 @@ writeResponse = (sessionContext, res) ->
|
|||||||
sessionContext.httpResponse.end(res.data)
|
sessionContext.httpResponse.end(res.data)
|
||||||
delete sessionContext.httpRequest
|
delete sessionContext.httpRequest
|
||||||
delete sessionContext.httpResponse
|
delete sessionContext.httpResponse
|
||||||
|
stats.concurrentRequests -= 1
|
||||||
|
|
||||||
|
|
||||||
recordFault = (sessionContext, fault, provisions, channels) ->
|
recordFault = (sessionContext, fault, provisions, channels) ->
|
||||||
@@ -575,12 +585,7 @@ sendAcsRequest = (sessionContext, id, acsRequest) ->
|
|||||||
writeResponse(sessionContext, res)
|
writeResponse(sessionContext, res)
|
||||||
|
|
||||||
|
|
||||||
getSession = (httpRequest, callback) ->
|
getSession = (httpRequest, sessionId, callback) ->
|
||||||
# Separation by comma is important as some devices don't comform to standard
|
|
||||||
COOKIE_REGEX = /\s*([a-zA-Z0-9\-_]+?)\s*=\s*"?([a-zA-Z0-9\-_]*?)"?\s*(,|;|$)/g
|
|
||||||
while match = COOKIE_REGEX.exec(httpRequest.headers.cookie)
|
|
||||||
sessionId = match[2] if match[1] == 'session'
|
|
||||||
|
|
||||||
return callback() if not sessionId?
|
return callback() if not sessionId?
|
||||||
|
|
||||||
sessionContext = currentSessions.get(httpRequest.connection)?[sessionId]
|
sessionContext = currentSessions.get(httpRequest.connection)?[sessionId]
|
||||||
@@ -618,6 +623,21 @@ onConnection = (socket) ->
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
setInterval(() ->
|
||||||
|
if stats.droppedRequests
|
||||||
|
logger.warn({
|
||||||
|
message: 'Worker overloaded'
|
||||||
|
droppedRequests: stats.droppedRequests
|
||||||
|
totalRequests: stats.totalRequests
|
||||||
|
initiatedSessions: stats.initiatedSessions
|
||||||
|
pid: process.pid
|
||||||
|
})
|
||||||
|
|
||||||
|
stats.totalRequests = 0
|
||||||
|
stats.droppedRequests = 0
|
||||||
|
stats.initiatedSessions = 0
|
||||||
|
, 10000).unref()
|
||||||
|
|
||||||
|
|
||||||
getDueTasksAndFaultsAndOperations = (deviceId, timestamp, callback) ->
|
getDueTasksAndFaultsAndOperations = (deviceId, timestamp, callback) ->
|
||||||
cache.get("#{deviceId}_tasks_faults_operations", (err, res) ->
|
cache.get("#{deviceId}_tasks_faults_operations", (err, res) ->
|
||||||
@@ -682,11 +702,25 @@ cacheDueTasksAndFaultsAndOperations = (deviceId, tasks, faults, operations, cach
|
|||||||
|
|
||||||
|
|
||||||
listener = (httpRequest, httpResponse) ->
|
listener = (httpRequest, httpResponse) ->
|
||||||
|
stats.totalRequests += 1
|
||||||
if httpRequest.method != 'POST'
|
if httpRequest.method != 'POST'
|
||||||
httpResponse.writeHead 405, {'Allow': 'POST'}
|
httpResponse.writeHead 405, {'Allow': 'POST'}
|
||||||
httpResponse.end('405 Method Not Allowed')
|
httpResponse.end('405 Method Not Allowed')
|
||||||
return
|
return
|
||||||
|
|
||||||
|
sessionId = null
|
||||||
|
# Separation by comma is important as some devices don't comform to standard
|
||||||
|
COOKIE_REGEX = /\s*([a-zA-Z0-9\-_]+?)\s*=\s*"?([a-zA-Z0-9\-_]*?)"?\s*(,|;|$)/g
|
||||||
|
while match = COOKIE_REGEX.exec(httpRequest.headers.cookie)
|
||||||
|
sessionId = match[2] if match[1] == 'session'
|
||||||
|
|
||||||
|
# If overloaded, ask CPE to retry in 60 seconds
|
||||||
|
if not sessionId and stats.concurrentRequests > MAX_CONCURRENT_REQUESTS
|
||||||
|
httpResponse.writeHead(503, {'Retry-after': 60})
|
||||||
|
httpResponse.end('503 Service Unavailable')
|
||||||
|
stats.droppedRequests += 1
|
||||||
|
return
|
||||||
|
|
||||||
if httpRequest.headers['content-encoding']?
|
if httpRequest.headers['content-encoding']?
|
||||||
switch httpRequest.headers['content-encoding']
|
switch httpRequest.headers['content-encoding']
|
||||||
when 'gzip'
|
when 'gzip'
|
||||||
@@ -700,6 +734,7 @@ listener = (httpRequest, httpResponse) ->
|
|||||||
else
|
else
|
||||||
stream = httpRequest
|
stream = httpRequest
|
||||||
|
|
||||||
|
stats.concurrentRequests += 1
|
||||||
chunks = []
|
chunks = []
|
||||||
bytes = 0
|
bytes = 0
|
||||||
|
|
||||||
@@ -721,15 +756,18 @@ listener = (httpRequest, httpResponse) ->
|
|||||||
stream.on('end', () ->
|
stream.on('end', () ->
|
||||||
rpc = null
|
rpc = null
|
||||||
parseWarnings = []
|
parseWarnings = []
|
||||||
getSession(httpRequest, f = (err, sessionContext) ->
|
getSession(httpRequest, sessionId, f = (err, sessionContext) ->
|
||||||
return throwError(err, httpResponse) if err
|
return throwError(err, httpResponse) if err
|
||||||
|
|
||||||
rpc ?= soap.request(httpRequest, sessionContext?.cwmpVersion, parseWarnings)
|
rpc ?= soap.request(httpRequest, sessionContext?.cwmpVersion, parseWarnings)
|
||||||
if not sessionContext?
|
if not sessionContext?
|
||||||
if rpc.cpeRequest?.name isnt 'Inform'
|
if rpc.cpeRequest?.name isnt 'Inform'
|
||||||
httpResponse.writeHead(400)
|
httpResponse.writeHead(400)
|
||||||
httpResponse.end('Session is expired')
|
httpResponse.end('Session is expired')
|
||||||
|
stats.concurrentRequests -= 1
|
||||||
return
|
return
|
||||||
|
|
||||||
|
stats.initiatedSessions += 1
|
||||||
deviceId = common.generateDeviceId(rpc.cpeRequest.deviceId)
|
deviceId = common.generateDeviceId(rpc.cpeRequest.deviceId)
|
||||||
return session.init(deviceId, rpc.cwmpVersion, rpc.sessionTimeout ? config.get('SESSION_TIMEOUT', deviceId), (err, sessionContext) ->
|
return session.init(deviceId, rpc.cwmpVersion, rpc.sessionTimeout ? config.get('SESSION_TIMEOUT', deviceId), (err, sessionContext) ->
|
||||||
return throwError(err, httpResponse) if err
|
return throwError(err, httpResponse) if err
|
||||||
|
|||||||
Reference in new issue
Block a user