Test runner hangs with RangeError: Maximum call stack size exceeded
See original GitHub issueCurrent behavior:
The following can be experienced when running cypress headlessly on a particular error.
RangeError: Maximum call stack size exceeded
at _hasBinary (/root/.cache/Cypress/3.3.1/Cypress/resources/app/packages/socket/node_modules/has-binary/index.js:25:22)
at _hasBinary (/root/.cache/Cypress/3.3.1/Cypress/resources/app/packages/socket/node_modules/has-binary/index.js:49:63)
at _hasBinary (/root/.cache/Cypress/3.3.1/Cypress/resources/app/packages/socket/node_modules/has-binary/index.js:49:63)
...
at hasBinary (/root/.cache/Cypress/3.3.1/Cypress/resources/app/packages/socket/node_modules/has-binary/index.js:58:10)
at /root/.cache/Cypress/3.3.1/Cypress/resources/app/packages/socket/node_modules/socket.io/lib/socket.js:373:16
at /root/.cache/Cypress/3.3.1/Cypress/resources/app/packages/server/lib/socket.js:312:22
at tryCatcher (/root/.cache/Cypress/3.3.1/Cypress/resources/app/packages/server/node_modules/bluebird/js/release/util.js:16:23)
at Promise._settlePromiseFromHandler (/root/.cache/Cypress/3.3.1/Cypress/resources/app/packages/server/node_modules/bluebird/js/release/promise.js:510:31)
at Promise._settlePromise (/root/.cache/Cypress/3.3.1/Cypress/resources/app/packages/server/node_modules/bluebird/js/release/promise.js:567:18)
at Promise._settlePromise0 (/root/.cache/Cypress/3.3.1/Cypress/resources/app/packages/server/node_modules/bluebird/js/release/promise.js:612:10)
at Promise._settlePromises (/root/.cache/Cypress/3.3.1/Cypress/resources/app/packages/server/node_modules/bluebird/js/release/promise.js:687:18)
at Async._drainQueue (/root/.cache/Cypress/3.3.1/Cypress/resources/app/packages/server/node_modules/bluebird/js/release/async.js:133:16)
at Async._drainQueues (/root/.cache/Cypress/3.3.1/Cypress/resources/app/packages/server/node_modules/bluebird/js/release/async.js:143:10)
at Immediate.Async.drainQueues (/root/.cache/Cypress/3.3.1/Cypress/resources/app/packages/server/node_modules/bluebird/js/release/async.js:17:14)
at runCallback (timers.js:789:20)
at tryOnImmediate (timers.js:751:5)
at processImmediate [as _immediateCallback] (timers.js:722:5)
This effectively hangs the process.
Desired behavior:
This should not happen.
Steps to reproduce: (app code and test code)
It is consistent in our test suite, but it seems that giving a minimally reproducible case is not easy, as merely changing the order of tests will not yield this error.
That said, tracing back the issue, here is what has been found:
The library request-promise-core on error throws the following Error object on a particular error (in our case ECONNREFUSED).
{
name: 'RequestError',
stack: 'RequestError: Error: connect ECONNREFUSED 0.0.0.0:443\n\
at new RequestError (/root/.cache/Cypress/3.3.1/Cypress/resources/app/packages/server/node_modules/request-promise-core/lib/errors.js:14:15)\n\
at Request.plumbing.callback (/root/.cache/Cypress/3.3.1/Cypress/resources/app/packages/server/node_modules/request-promise-core/lib/plumbing.js:87:29)\n\
at Request.RP$callback [as _callback] (/root/.cache/Cypress/3.3.1/Cypress/resources/app/packages/server/node_modules/request-promise-core/lib/plumbing.js:46:31)\n\
at self.callback (/root/.cache/Cypress/3.3.1/Cypress/resources/app/packages/server/node_modules/request/request.js:185:22)\n\
at emitOne (events.js:116:13)\n\
at Request.emit (events.js:211:7)\n\
at Request.onRequestError (/root/.cache/Cypress/3.3.1/Cypress/resources/app/packages/server/node_modules/request/request.js:884:8)\n\
at emitOne (events.js:121:20)\n\
at ClientRequest.emit (events.js:211:7)\n\
at TLSSocket.socketErrorListener (_http_client.js:387:9)\n\
at emitOne (events.js:116:13)\n\
at TLSSocket.emit (events.js:211:7)\n\
at emitErrorNT (internal/streams/destroy.js:64:8)\n\
at _combinedTickCallback (internal/process/next_tick.js:138:11)\n\
at process._tickCallback (internal/process/next_tick.js:180:9)\n\
',
message: 'Error: connect ECONNREFUSED 0.0.0.0:443',
cause:
{ Error: connect ECONNREFUSED 0.0.0.0:443
at Object._errnoException (util.js:1024:11)
at _exceptionWithHostPort (util.js:1046:20)
at TCPConnectWrap.afterConnect [as oncomplete] (net.js:1182:14)
code: 'ECONNREFUSED',
errno: 'ECONNREFUSED',
syscall: 'connect',
address: '0.0.0.0',
port: 443 },
error:
{ Error: connect ECONNREFUSED 0.0.0.0:443
at Object._errnoException (util.js:1024:11)
at _exceptionWithHostPort (util.js:1046:20)
at TCPConnectWrap.afterConnect [as oncomplete] (net.js:1182:14)
code: 'ECONNREFUSED',
errno: 'ECONNREFUSED',
syscall: 'connect',
address: '0.0.0.0',
port: 443 },
options:
{ timeout: 90000,
agent:
CombinedAgent {
familyCache: { 'localhost': 4 },
httpAgent:
HttpAgent {
domain: null,
_events: { free: [Function] },
_eventsCount: 1,
_maxListeners: undefined,
defaultPort: 80,
protocol: 'http:',
options: { keepAlive: true, path: null },
requests: {},
sockets: {},
freeSockets: {},
keepAliveMsecs: 1000,
keepAlive: true,
maxSockets: Infinity,
maxFreeSockets: 256,
httpsAgent:
Agent {
domain: null,
_events: { free: [Function] },
_eventsCount: 1,
_maxListeners: undefined,
defaultPort: 443,
protocol: 'https:',
options: { keepAlive: true, path: null },
requests: {},
sockets: {},
freeSockets: {},
keepAliveMsecs: 1000,
keepAlive: true,
maxSockets: Infinity,
maxFreeSockets: 256,
maxCachedSessions: 100,
_sessionCache: { map: {}, list: [] } } },
httpsAgent:
HttpsAgent {
domain: null,
_events: { free: [Function] },
_eventsCount: 1,
_maxListeners: undefined,
defaultPort: 443,
protocol: 'https:',
options: { keepAlive: true, path: null },
requests: {},
sockets: {},
freeSockets:
{ 'localhost:443::4::::::false::':
[ TLSSocket {
_tlsOptions:
{ pipe: false,
secureContext: SecureContext { context: SecureContext {} },
isServer: false,
requestCert: true,
rejectUnauthorized: false,
session: undefined,
NPNProtocols: undefined,
ALPNProtocols: undefined,
requestOCSP: undefined },
_secureEstablished: true,
_securePending: false,
_newSessionPending: false,
_controlReleased: true,
_SNICallback: null,
servername: null,
npnProtocol: false,
alpnProtocol: false,
authorized: true,
authorizationError: null,
encrypted: true,
_events:
{ close:
[ [Function],
{ [Function: bound onceWrapper] listener: [Function] },
[Function: onClose] ],
end: { [Function: bound onceWrapper] listener: [Function: onend] },
finish: [Function: onSocketFinish],
_socketEnd: [Function: onSocketEnd],
secure: [Function],
free: [Function: onFree],
agentRemove: [Function: onRemove],
drain: [Function: ondrain],
error: { [Function: bound onceWrapper] listener: [Function: freeSocketErrorListener] } },
_eventsCount: 9,
connecting: false,
_hadError: false,
_handle:
TLSWrap {
_parent:
TCP {
reading: [Getter/Setter],
owner: [Circular],
onread: null,
onconnection: null,
writeQueueSize: 0 },
_parentWrap: undefined,
_secureContext: SecureContext { context: SecureContext {} },
reading: true,
owner: [Circular],
onread: [Function: onread],
writeQueueSize: 0,
onhandshakestart: [Function],
onhandshakedone: [Function],
onocspresponse: [Function],
onerror: [Function] },
_parent: null,
_host: 'localhost',
_readableState:
ReadableState {
objectMode: false,
highWaterMark: 16384,
buffer: BufferList { head: null, tail: null, length: 0 },
length: 0,
pipes: null,
pipesCount: 0,
flowing: true,
ended: false,
endEmitted: false,
reading: true,
sync: false,
needReadable: true,
emittedReadable: false,
readableListening: false,
resumeScheduled: false,
destroyed: false,
defaultEncoding: 'utf8',
awaitDrain: 0,
readingMore: false,
decoder: null,
encoding: null },
readable: true,
domain: null,
_maxListeners: undefined,
_writableState:
WritableState {
objectMode: false,
highWaterMark: 16384,
finalCalled: false,
needDrain: false,
ending: false,
ended: false,
finished: false,
destroyed: false,
decodeStrings: false,
defaultEncoding: 'utf8',
length: 0,
writing: false,
corked: 0,
sync: false,
bufferProcessing: false,
onwrite: [Function: bound onwrite],
writecb: null,
writelen: 0,
bufferedRequest: null,
lastBufferedRequest: null,
pendingcb: 0,
prefinished: false,
errorEmitted: false,
bufferedRequestCount: 0,
corkedRequestsFree:
{ next: null,
entry: null,
finish: [Function: bound onCorkedFinish] } },
writable: true,
allowHalfOpen: false,
_bytesDispatched: 348,
_sockname: null,
_pendingData: null,
_pendingEncoding: '',
server: undefined,
_server: null,
ssl:
TLSWrap {
_parent:
TCP {
reading: [Getter/Setter],
owner: [Circular],
onread: null,
onconnection: null,
writeQueueSize: 0 },
_parentWrap: undefined,
_secureContext: SecureContext { context: SecureContext {} },
reading: true,
owner: [Circular],
onread: [Function: onread],
writeQueueSize: 0,
onhandshakestart: [Function],
onhandshakedone: [Function],
onocspresponse: [Function],
onerror: [Function] },
_requestCert: true,
_rejectUnauthorized: false,
parser: null,
_httpMessage: null,
read: [Function],
_consuming: true,
_idleTimeout: -1,
_idleNext: null,
_idlePrev: null,
_idleStart: 41856,
_destroyed: false,
[Symbol(asyncId)]: -1,
[Symbol(bytesRead)]: 0,
[Symbol(asyncId)]: 3173,
[Symbol(triggerAsyncId)]: 3163 } ] },
keepAliveMsecs: 1000,
keepAlive: true,
maxSockets: Infinity,
maxFreeSockets: 256,
maxCachedSessions: 100,
_sessionCache:
{ map:
{ 'localhost:443::4::::::false::': <Buffer 30 82 08 3c 02 01 01 02 02 03 03 04 02 c0 2f 04 20 e7 f6 ff ce 61 67 f3 90 b7 4f 56 d1 a5 0f 5f 09 65 50 61 20 74 54 db d8 bb c9 59 f6 d7 c4 e4 46 04 ... > },
list:
[ 'localhost:443::4::::::false::' ] } } },
headers:
{ Connection: 'keep-alive',
'user-agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Cypress/3.3.1 Chrome/61.0.3163.100 Electron/2.0.18 Safari/537.36',
accept: '*/*' },
proxy: null,
url: 'https://localhost/',
method: 'GET',
gzip: true,
followRedirect: [Function],
failOnStatusCode: false,
retryOnNetworkFailure: true,
retryOnStatusCodeFailure: false,
jar:
{ _jar: CookieJar { enableLooseMode: true, store: { idx: {} } },
toJSON: [Function: toJSON],
setCookie: [Function: setCookie],
getCookieString: [Function: getCookieString],
getCookies: [Function: getCookies] },
cookies: true,
strictSSL: false,
simple: false,
resolveWithFullResponse: true,
followAllRedirects: true,
requestId: 'request3',
retryIntervals: [ 0, 1000, 2000, 2000 ],
delaysRemaining: [],
callback: [Function: RP$callback],
transform: undefined,
transform2xxOnly: false },
response: undefined
}
The important part is that this object contains circular references:
owner: [Circular],
On request error, this object is sent to socket.io’s callback function https://github.com/cypress-io/cypress/blob/develop/packages/server/lib/socket.coffee#L315-L319
This is a problem because Socket.io scans arguments for binary data that doesn’t deal with circular reference. https://github.com/socketio/socket.io/blob/master/lib/socket.js#L374-L391 https://github.com/expanse-org/socketio-app/blob/master/node_modules/has-binary2/index.js
There is already some filtering done to errors captured and raised back to the execution flow. https://github.com/cypress-io/cypress/blob/develop/packages/server/lib/errors.coffee#L808-L828
An additional operation to would be to make sure that it doesn’t contain circular reference. I’d suggest using a library like fclone for this. https://github.com/soyuka/fclone
Versions
- Cypress 3.3.1
- Electron 61 (headless)
- CentOS 7
Note: OSX with electron or chrome did not reproduce the issue.
Issue Analytics
- State:
- Created 4 years ago
- Reactions:6
- Comments:23 (8 by maintainers)
Top GitHub Comments
Do you have an ETA for release on #4469?
Released in
3.3.2
.