@steadybrew/postgresjs 4.0.0-rc1 → 4.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +37 -11
- package/cf/src/connection.js +307 -322
- package/cf/src/errors.js +2 -2
- package/cf/src/index.js +127 -80
- package/cf/src/query.js +18 -3
- package/cf/src/timeout.js +5 -0
- package/cf/src/transport.js +88 -0
- package/cf/src/types.js +2 -2
- package/cjs/src/connection.js +307 -322
- package/cjs/src/errors.js +2 -2
- package/cjs/src/index.js +127 -80
- package/cjs/src/query.js +18 -3
- package/cjs/src/timeout.js +5 -0
- package/cjs/src/transport.js +88 -0
- package/cjs/src/types.js +2 -2
- package/package.json +1 -1
- package/src/connection.js +307 -322
- package/src/errors.js +2 -2
- package/src/index.js +127 -80
- package/src/query.js +18 -3
- package/src/timeout.js +5 -0
- package/src/transport.js +88 -0
- package/src/types.js +2 -2
- package/types/index.d.ts +5 -1
package/cf/src/connection.js
CHANGED
|
@@ -10,6 +10,8 @@ import { stringify, handleValue, addArrayType } from './types.js'
|
|
|
10
10
|
import { Errors } from './errors.js'
|
|
11
11
|
import Result from './result.js'
|
|
12
12
|
import Queue from './queue.js'
|
|
13
|
+
import clampedTimeout from './timeout.js'
|
|
14
|
+
import { SSLRequest, tlsConfig, cancelRequest } from './transport.js'
|
|
13
15
|
import { Query, CLOSE } from './query.js'
|
|
14
16
|
import b from './bytes.js'
|
|
15
17
|
|
|
@@ -19,12 +21,12 @@ let uid = 1
|
|
|
19
21
|
|
|
20
22
|
const Sync = b().S().end()
|
|
21
23
|
, Flush = b().H().end()
|
|
22
|
-
, SSLRequest = b().i32(8).i32(80877103).end(8)
|
|
23
24
|
, ExecuteUnnamed = Buffer.concat([b().E().str(b.N).i32(0).end(), Sync])
|
|
24
25
|
, DescribeUnnamed = b().D().str('S').str(b.N).end()
|
|
25
26
|
, noop = () => { /* noop */ }
|
|
26
27
|
|
|
27
|
-
const Phase = { Closed: 0, Backoff: 1,
|
|
28
|
+
const Phase = { Closed: 0, Backoff: 1, Connecting: 2, Initializing: 3, Ready: 4, Draining: 5, Closing: 6 }
|
|
29
|
+
const phaseNames = Object.keys(Phase)
|
|
28
30
|
|
|
29
31
|
const retryRoutines = new Set([
|
|
30
32
|
'FetchPreparedStatement',
|
|
@@ -76,18 +78,20 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
|
|
|
76
78
|
const sent = Queue()
|
|
77
79
|
, id = uid++
|
|
78
80
|
, backend = { pid: null, secret: null }
|
|
79
|
-
, idleTimer = timer(end, options.idle_timeout)
|
|
80
|
-
, lifeTimer = timer(end, options.max_lifetime)
|
|
81
|
+
, idleTimer = timer(() => dispatch('end'), options.idle_timeout)
|
|
82
|
+
, lifeTimer = timer(() => dispatch('end'), options.max_lifetime)
|
|
81
83
|
|
|
82
84
|
let socket = null
|
|
83
85
|
, phase = Phase.Closed
|
|
84
86
|
, generation = 0
|
|
85
87
|
, acquisition = null
|
|
86
|
-
, endRequested = false
|
|
87
88
|
, endWaiters = []
|
|
88
89
|
, inheritedBackoff = null
|
|
89
90
|
, backoffTimer = null
|
|
90
|
-
,
|
|
91
|
+
, backoffUntil = 0
|
|
92
|
+
, attemptTimer = null
|
|
93
|
+
, deadlineTimer = null
|
|
94
|
+
, closeTimer = null
|
|
91
95
|
, errorResponse = null
|
|
92
96
|
, result = new Result()
|
|
93
97
|
, incoming = Buffer.alloc(0)
|
|
@@ -110,38 +114,153 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
|
|
|
110
114
|
, query = null
|
|
111
115
|
, final = null
|
|
112
116
|
|
|
117
|
+
const check = globalThis[Symbol.for('postgres.js:check')]
|
|
118
|
+
let depth = 0
|
|
119
|
+
|
|
120
|
+
const starting = {
|
|
121
|
+
acquire: refuse,
|
|
122
|
+
deadline: expire,
|
|
123
|
+
attemptTimeout: () => afterFailure(Errors.connection('CONNECT_TIMEOUT', options, socket), 'timeout'),
|
|
124
|
+
socketError: err => afterFailure(err, 'socket'),
|
|
125
|
+
socketClose: () => afterFailure(errorResponse || Errors.connection('CONNECTION_CLOSED', options, socket), 'dropped'),
|
|
126
|
+
protocolError: err => afterFailure(err, 'protocol'),
|
|
127
|
+
authFailed: enterClosed,
|
|
128
|
+
writable: noop,
|
|
129
|
+
end: () => endAcquisition(null),
|
|
130
|
+
terminate: () => enterClosed(Errors.connection('CONNECTION_DESTROYED', options, socket))
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const on = {
|
|
134
|
+
[Phase.Closed]: {
|
|
135
|
+
acquire,
|
|
136
|
+
execute: refuse,
|
|
137
|
+
end: () => endWaiters.splice(0).forEach(resolve => resolve()),
|
|
138
|
+
terminate: noop
|
|
139
|
+
},
|
|
140
|
+
[Phase.Backoff]: {
|
|
141
|
+
acquire: refuse,
|
|
142
|
+
execute: refuse,
|
|
143
|
+
backoffElapsed: enterConnecting,
|
|
144
|
+
deadline: expire,
|
|
145
|
+
end: () => (inheritedBackoff = { at: performance.now(), delay: backoffUntil - performance.now() }, endAcquisition(acquisition.lastError)),
|
|
146
|
+
terminate: () => enterClosed(Errors.connection('CONNECTION_DESTROYED', options, socket))
|
|
147
|
+
},
|
|
148
|
+
[Phase.Connecting]: {
|
|
149
|
+
...starting,
|
|
150
|
+
execute: refuse,
|
|
151
|
+
socketCreated: attach,
|
|
152
|
+
factoryFailed: err => afterFailure(err, 'factory'),
|
|
153
|
+
socketReady: () => ssl ? negotiate() : authenticate(),
|
|
154
|
+
sslReply: x => x[0] === 83
|
|
155
|
+
? upgrade()
|
|
156
|
+
: ssl === 'prefer'
|
|
157
|
+
? authenticate()
|
|
158
|
+
: afterFailure(Errors.generic('SSL_NOT_SUPPORTED', 'The server does not support SSL connections'), 'protocol'),
|
|
159
|
+
secure: authenticate,
|
|
160
|
+
serverError: err => afterFailure(err, 'rejected'),
|
|
161
|
+
ready: () => (transition(Phase.Initializing), initialized())
|
|
162
|
+
},
|
|
163
|
+
[Phase.Initializing]: {
|
|
164
|
+
...starting,
|
|
165
|
+
execute: q => q.initialization ? submit(q) : refuse(q),
|
|
166
|
+
serverError: err => afterFailure(err, 'dropped'),
|
|
167
|
+
initFailed: enterClosed,
|
|
168
|
+
ready: initialized
|
|
169
|
+
},
|
|
170
|
+
[Phase.Ready]: {
|
|
171
|
+
acquire: refuse,
|
|
172
|
+
execute: submit,
|
|
173
|
+
ready: () => pipelined() || (connection.owner ? connection.owner.next() : onopen(connection)),
|
|
174
|
+
serverError: lost,
|
|
175
|
+
socketError: lost,
|
|
176
|
+
socketClose: hadError => lost(closedBy(errorResponse), hadError),
|
|
177
|
+
protocolError: errored,
|
|
178
|
+
writable: () => !query && !connection.owner && onopen(connection),
|
|
179
|
+
end: () => idle() && !connection.owner
|
|
180
|
+
? closing()
|
|
181
|
+
: (transition(Phase.Draining), connection.owner || onend(connection)),
|
|
182
|
+
terminate: () => idle() ? enterClosed() : enterClosed(Errors.connection('CONNECTION_DESTROYED', options, socket)),
|
|
183
|
+
release: () => onopen(connection)
|
|
184
|
+
},
|
|
185
|
+
[Phase.Draining]: {
|
|
186
|
+
acquire: refuse,
|
|
187
|
+
execute: submit,
|
|
188
|
+
ready: () => pipelined() || (connection.owner ? connection.owner.next() : drained()),
|
|
189
|
+
serverError: lost,
|
|
190
|
+
socketError: lost,
|
|
191
|
+
socketClose: hadError => lost(closedBy(errorResponse), hadError),
|
|
192
|
+
protocolError: errored,
|
|
193
|
+
writable: noop,
|
|
194
|
+
end: noop,
|
|
195
|
+
terminate: () => idle() ? enterClosed() : enterClosed(Errors.connection('CONNECTION_DESTROYED', options, socket)),
|
|
196
|
+
release: () => idle() ? drained() : onend(connection)
|
|
197
|
+
},
|
|
198
|
+
[Phase.Closing]: {
|
|
199
|
+
acquire: refuse,
|
|
200
|
+
execute: refuse,
|
|
201
|
+
ready: noop,
|
|
202
|
+
serverError: noop,
|
|
203
|
+
socketError: noop,
|
|
204
|
+
socketClose: hadError => hadError ? lost(Errors.connection('CONNECTION_CLOSED', options, socket)) : enterClosed(),
|
|
205
|
+
closeTimeout: () => enterClosed(),
|
|
206
|
+
protocolError: errored,
|
|
207
|
+
writable: noop,
|
|
208
|
+
end: noop,
|
|
209
|
+
terminate: () => enterClosed()
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
113
213
|
const connection = {
|
|
114
214
|
queue: queues.closed,
|
|
115
215
|
idleTimer,
|
|
116
|
-
connect: acquire,
|
|
117
|
-
terminate,
|
|
216
|
+
connect: request => dispatch('acquire', request),
|
|
217
|
+
terminate: () => dispatch('terminate'),
|
|
118
218
|
execute,
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
219
|
+
release: () => dispatch('release'),
|
|
220
|
+
end: () => {
|
|
221
|
+
const done = new Promise(resolve => endWaiters.push(resolve))
|
|
222
|
+
dispatch('end')
|
|
223
|
+
return done
|
|
224
|
+
},
|
|
225
|
+
expired: () => idleTimer.expired() || lifeTimer.expired(),
|
|
226
|
+
owner: null,
|
|
122
227
|
count: 0,
|
|
123
228
|
id
|
|
124
229
|
}
|
|
125
230
|
|
|
231
|
+
check && Object.defineProperty(connection, Symbol.for('postgres.js:phase'), { get: () => phaseNames[phase] })
|
|
232
|
+
check && check('created', connection, queues)
|
|
233
|
+
|
|
126
234
|
queues.closed && queues.closed.push(connection)
|
|
127
235
|
|
|
128
236
|
return connection
|
|
129
237
|
|
|
130
238
|
function transition(next) {
|
|
239
|
+
check && check('edge', connection, phaseNames[phase], phaseNames[next])
|
|
131
240
|
generation++
|
|
132
241
|
phase = next
|
|
133
242
|
}
|
|
134
243
|
|
|
135
|
-
function
|
|
136
|
-
|
|
244
|
+
function dispatch(event, arg) {
|
|
245
|
+
const handler = on[phase][event]
|
|
246
|
+
if (!handler)
|
|
247
|
+
return check && check('unhandled', connection, phaseNames[phase], event)
|
|
248
|
+
|
|
249
|
+
depth++
|
|
250
|
+
try {
|
|
251
|
+
return handler(arg)
|
|
252
|
+
} finally {
|
|
253
|
+
depth--
|
|
254
|
+
check && depth === 0 && check('settled', connection, queues)
|
|
255
|
+
}
|
|
137
256
|
}
|
|
138
257
|
|
|
139
|
-
function
|
|
140
|
-
|
|
258
|
+
function refuse(request) {
|
|
259
|
+
queryError(request, Errors.connection('CONNECTION_CLOSED', options))
|
|
141
260
|
}
|
|
142
261
|
|
|
143
262
|
function drained() {
|
|
144
|
-
ondrain(connection) || closing(
|
|
263
|
+
ondrain(connection) || closing()
|
|
145
264
|
}
|
|
146
265
|
|
|
147
266
|
function idle() {
|
|
@@ -152,47 +271,38 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
|
|
|
152
271
|
return (typeof backoff === 'function' ? backoff(options.shared.retries) : backoff) * 1000
|
|
153
272
|
}
|
|
154
273
|
|
|
155
|
-
function
|
|
156
|
-
|
|
157
|
-
|
|
274
|
+
function standbyPass() {
|
|
275
|
+
return target_session_attrs === 'prefer-standby' && host.length > 1 && acquisition.pass === 'standby'
|
|
276
|
+
}
|
|
158
277
|
|
|
159
|
-
|
|
278
|
+
function acquire(owner) {
|
|
279
|
+
acquisition = { owner, ending: false, pass: 'standby', hostsTried: 0, attempting: null, mismatches: [], lastError: null }
|
|
160
280
|
const wait = inheritedBackoff ? inheritedBackoff.at + inheritedBackoff.delay - performance.now() : 0
|
|
161
281
|
inheritedBackoff = null
|
|
162
|
-
wait > 0 ? enterBackoff(wait) :
|
|
282
|
+
wait > 0 ? enterBackoff(wait) : enterConnecting()
|
|
163
283
|
}
|
|
164
284
|
|
|
165
285
|
function enterBackoff(ms) {
|
|
166
286
|
transition(Phase.Backoff)
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
attempt === generation && (a.expired
|
|
174
|
-
? enterClosed(a.lastError || Errors.connection('CONNECT_TIMEOUT', options))
|
|
175
|
-
: enterOpening())
|
|
176
|
-
}, Math.min(ms, left))
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
function enterOpening() {
|
|
180
|
-
transition(Phase.Opening)
|
|
287
|
+
backoffUntil = performance.now() + ms
|
|
288
|
+
backoffTimer = clampedTimeout(() => (backoffTimer = null, dispatch('backoffElapsed')), ms)
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function enterConnecting() {
|
|
292
|
+
transition(Phase.Connecting)
|
|
181
293
|
const a = acquisition
|
|
182
294
|
const attempt = generation
|
|
183
295
|
const ms = options.connect_timeout * 1000
|
|
184
296
|
if (ms) {
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
const left = a.deadline - now
|
|
188
|
-
connectTimer = setTimeout(() => connectTimedOut(left <= ms), Math.min(ms, left))
|
|
297
|
+
deadlineTimer === null && (deadlineTimer = clampedTimeout(() => (deadlineTimer = null, dispatch('deadline')), ms * host.length))
|
|
298
|
+
host.length > 1 && (attemptTimer = clampedTimeout(() => (attemptTimer = null, dispatch('attemptTimeout')), ms))
|
|
189
299
|
}
|
|
190
300
|
a.hostsTried++
|
|
191
301
|
backendParameters = {}
|
|
192
302
|
Promise.resolve()
|
|
193
303
|
.then(() => options.socket ? options.socket(options) : new net.Socket())
|
|
194
|
-
.then(created => attempt === generation ?
|
|
195
|
-
.catch(err => attempt === generation &&
|
|
304
|
+
.then(created => attempt === generation ? dispatch('socketCreated', created) : dispose(created))
|
|
305
|
+
.catch(err => attempt === generation && dispatch('factoryFailed', err))
|
|
196
306
|
}
|
|
197
307
|
|
|
198
308
|
function dispose(created) {
|
|
@@ -200,16 +310,21 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
|
|
|
200
310
|
created.destroy()
|
|
201
311
|
}
|
|
202
312
|
|
|
313
|
+
function listen(s) {
|
|
314
|
+
s.on('error', err => dispatch('socketError', err))
|
|
315
|
+
s.on('close', hadError => dispatch('socketClose', hadError))
|
|
316
|
+
s.on('drain', () => dispatch('writable'))
|
|
317
|
+
}
|
|
318
|
+
|
|
203
319
|
function attach(created) {
|
|
320
|
+
listen(created)
|
|
204
321
|
socket = created
|
|
205
|
-
|
|
206
|
-
created.on('close', closed)
|
|
207
|
-
created.on('drain', drain)
|
|
322
|
+
acquisition.attempting = options.socket ? 'custom socket' : options.path || host[hostIndex] + ':' + port[hostIndex]
|
|
208
323
|
|
|
209
324
|
if (options.socket)
|
|
210
|
-
return
|
|
325
|
+
return dispatch('socketReady')
|
|
211
326
|
|
|
212
|
-
created.on('connect',
|
|
327
|
+
created.on('connect', () => dispatch('socketReady'))
|
|
213
328
|
|
|
214
329
|
if (options.path)
|
|
215
330
|
return created.connect(options.path)
|
|
@@ -223,110 +338,96 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
|
|
|
223
338
|
}
|
|
224
339
|
|
|
225
340
|
function negotiate() {
|
|
226
|
-
transition(Phase.Negotiating)
|
|
227
341
|
if (sslnegotiation === 'direct')
|
|
228
342
|
return upgrade()
|
|
229
343
|
|
|
230
|
-
|
|
231
|
-
socket.once('data', x => attempt === generation && (
|
|
232
|
-
x[0] === 83
|
|
233
|
-
? upgrade()
|
|
234
|
-
: ssl === 'prefer'
|
|
235
|
-
? authenticate()
|
|
236
|
-
: fail(Errors.generic('SSL_NOT_SUPPORTED', 'The server does not support SSL connections'))
|
|
237
|
-
))
|
|
344
|
+
socket.once('data', x => dispatch('sslReply', x))
|
|
238
345
|
write(SSLRequest)
|
|
239
346
|
}
|
|
240
347
|
|
|
241
348
|
function upgrade() {
|
|
242
349
|
try {
|
|
243
350
|
const raw = socket
|
|
244
|
-
const config = tlsConfig(raw)
|
|
351
|
+
const config = tlsConfig(options, raw)
|
|
245
352
|
raw.removeAllListeners()
|
|
246
353
|
socket = tls.connect(config)
|
|
247
|
-
socket.on('secureConnect',
|
|
248
|
-
socket
|
|
249
|
-
socket.on('close', closed)
|
|
250
|
-
socket.on('drain', drain)
|
|
354
|
+
socket.on('secureConnect', () => dispatch('secure'))
|
|
355
|
+
listen(socket)
|
|
251
356
|
} catch (err) {
|
|
252
|
-
|
|
357
|
+
afterFailure(err, 'protocol')
|
|
253
358
|
}
|
|
254
359
|
}
|
|
255
360
|
|
|
256
|
-
function tlsConfig(raw) {
|
|
257
|
-
const config = {
|
|
258
|
-
socket: raw,
|
|
259
|
-
servername: net.isIP(raw.host) ? undefined : raw.host
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
if (sslnegotiation === 'direct')
|
|
263
|
-
config.ALPNProtocols = ['postgresql']
|
|
264
|
-
|
|
265
|
-
if (ssl === 'require' || ssl === 'allow' || ssl === 'prefer')
|
|
266
|
-
config.rejectUnauthorized = false
|
|
267
|
-
else if (typeof ssl === 'object')
|
|
268
|
-
Object.assign(config, ssl)
|
|
269
|
-
|
|
270
|
-
return config
|
|
271
|
-
}
|
|
272
|
-
|
|
273
361
|
function authenticate() {
|
|
274
|
-
transition(Phase.Authenticating)
|
|
275
362
|
try {
|
|
276
363
|
statements = {}
|
|
277
364
|
needsTypes = options.fetch_types
|
|
278
365
|
statementId = Math.random().toString(36).slice(2)
|
|
279
366
|
statementCount = 1
|
|
280
|
-
lifeTimer.start()
|
|
281
367
|
socket.on('data', data)
|
|
282
368
|
keep_alive && socket.setKeepAlive && socket.setKeepAlive(true, 1000 * keep_alive)
|
|
283
369
|
write(StartupMessage())
|
|
284
370
|
} catch (err) {
|
|
285
|
-
|
|
371
|
+
afterFailure(err, 'protocol')
|
|
286
372
|
}
|
|
287
373
|
}
|
|
288
374
|
|
|
289
|
-
function
|
|
290
|
-
|
|
291
|
-
if (!starting())
|
|
292
|
-
return
|
|
293
|
-
expiring && (acquisition.expired = true)
|
|
294
|
-
fail(Errors.connection('CONNECT_TIMEOUT', options, socket), true)
|
|
375
|
+
function expire() {
|
|
376
|
+
enterClosed(acquisition.lastError || Errors.connection('CONNECT_TIMEOUT', options, socket))
|
|
295
377
|
}
|
|
296
378
|
|
|
297
|
-
function
|
|
379
|
+
function mismatched(reason) {
|
|
298
380
|
const a = acquisition
|
|
299
|
-
a.
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
detach()
|
|
304
|
-
resetSocketState(err)
|
|
305
|
-
|
|
306
|
-
if (endRequested)
|
|
307
|
-
return enterClosed(err)
|
|
308
|
-
|
|
309
|
-
if (a.expired || (a.deadline !== null && performance.now() >= a.deadline))
|
|
310
|
-
return enterClosed(a.lastError)
|
|
381
|
+
a.mismatches.push(a.attempting + ' ' + reason)
|
|
382
|
+
afterFailure(closedBy(Errors.generic('TARGET_SESSION_ATTRS',
|
|
383
|
+
'No host matched target_session_attrs=' + target_session_attrs + ': ' + a.mismatches.join(', '))), 'mismatch')
|
|
384
|
+
}
|
|
311
385
|
|
|
312
|
-
|
|
313
|
-
|
|
386
|
+
function afterFailure(err, cause) {
|
|
387
|
+
const a = acquisition
|
|
388
|
+
a.lastError = cause === 'timeout' && a.lastError ? a.lastError : err
|
|
314
389
|
|
|
315
|
-
|
|
316
|
-
if (!persist) {
|
|
390
|
+
if (a.ending) {
|
|
317
391
|
inheritedBackoff = { at: performance.now(), delay: backoffMs() }
|
|
318
392
|
return enterClosed(err)
|
|
319
393
|
}
|
|
320
394
|
|
|
321
|
-
|
|
322
|
-
|
|
395
|
+
let delay = 0
|
|
396
|
+
if (a.hostsTried === host.length) {
|
|
397
|
+
a.hostsTried = 0
|
|
398
|
+
const mismatches = a.mismatches
|
|
399
|
+
a.mismatches = []
|
|
400
|
+
if (standbyPass()) {
|
|
401
|
+
a.pass = 'any'
|
|
402
|
+
} else {
|
|
403
|
+
a.pass = 'standby'
|
|
404
|
+
options.shared.retries++
|
|
405
|
+
if (host.length > 1 && mismatches.length === host.length) {
|
|
406
|
+
inheritedBackoff = { at: performance.now(), delay: backoffMs() }
|
|
407
|
+
return enterClosed(err)
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
// A single endpoint that is read-only now is often mid-failover behind one DNS name, so retry until the deadline.
|
|
411
|
+
if (host.length === 1 && cause !== 'dropped' && cause !== 'mismatch') {
|
|
412
|
+
inheritedBackoff = { at: performance.now(), delay: backoffMs() }
|
|
413
|
+
return enterClosed(err)
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
delay = backoffMs()
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
clearTimeout(attemptTimer)
|
|
421
|
+
attemptTimer = null
|
|
422
|
+
endSession(err)
|
|
423
|
+
enterBackoff(delay)
|
|
323
424
|
}
|
|
324
425
|
|
|
325
|
-
function
|
|
326
|
-
clearTimeout(connectTimer)
|
|
327
|
-
connectTimer = null
|
|
328
|
-
idleTimer.cancel()
|
|
426
|
+
function endSession(err) {
|
|
329
427
|
lifeTimer.cancel()
|
|
428
|
+
clearTimeout(closeTimer)
|
|
429
|
+
closeTimer = null
|
|
430
|
+
clearImmediate(nextWriteTimer)
|
|
330
431
|
incoming = Buffer.alloc(0)
|
|
331
432
|
remaining = 0
|
|
332
433
|
incomings = null
|
|
@@ -336,117 +437,85 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
|
|
|
336
437
|
socket.destroy()
|
|
337
438
|
socket = null
|
|
338
439
|
}
|
|
440
|
+
stream && (stream.destroy(err), stream = null)
|
|
441
|
+
final && (final(err), final = null)
|
|
442
|
+
query && queryError(query, err)
|
|
443
|
+
while (sent.length) {
|
|
444
|
+
const pending = sent.shift()
|
|
445
|
+
queryError(pending, err)
|
|
446
|
+
pending.cancelled && pending.cancelled.resolve()
|
|
447
|
+
}
|
|
448
|
+
query = results = errorResponse = chunk = nextWriteTimer = null
|
|
449
|
+
result = new Result()
|
|
450
|
+
rows = 0
|
|
451
|
+
nonce = serverSignature = null
|
|
339
452
|
}
|
|
340
453
|
|
|
341
|
-
function enterClosed(err = Errors.connection('CONNECTION_CLOSED', options, socket)) {
|
|
342
|
-
if (phase === Phase.Closed)
|
|
343
|
-
return
|
|
344
|
-
|
|
454
|
+
function enterClosed(err = Errors.connection('CONNECTION_CLOSED', options, socket), reported = err) {
|
|
345
455
|
const a = acquisition
|
|
346
456
|
const waiters = endWaiters
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
detach()
|
|
350
|
-
resetSocketState(err)
|
|
457
|
+
clearAcquisitionTimers()
|
|
458
|
+
endSession(err)
|
|
351
459
|
acquisition = null
|
|
352
|
-
endRequested = false
|
|
353
460
|
endWaiters = []
|
|
354
461
|
transition(Phase.Closed)
|
|
355
462
|
a && queryError(a.owner, err)
|
|
356
463
|
waiters.forEach(resolve => resolve())
|
|
357
|
-
onclose(connection,
|
|
464
|
+
onclose(connection, reported)
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
function clearAcquisitionTimers() {
|
|
468
|
+
clearTimeout(attemptTimer)
|
|
469
|
+
attemptTimer = null
|
|
470
|
+
clearTimeout(deadlineTimer)
|
|
471
|
+
deadlineTimer = null
|
|
472
|
+
clearTimeout(backoffTimer)
|
|
473
|
+
backoffTimer = null
|
|
358
474
|
}
|
|
359
475
|
|
|
360
|
-
function closing(
|
|
476
|
+
function closing() {
|
|
361
477
|
transition(Phase.Closing)
|
|
362
|
-
|
|
478
|
+
onend(connection)
|
|
363
479
|
lifeTimer.cancel()
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
480
|
+
if (socket.readyState === 'open') {
|
|
481
|
+
socket.end(b().X().end())
|
|
482
|
+
const ms = options.connect_timeout * 1000
|
|
483
|
+
ms && (closeTimer = clampedTimeout(() => (closeTimer = null, dispatch('closeTimeout')), ms))
|
|
484
|
+
} else {
|
|
485
|
+
socket.destroy()
|
|
486
|
+
}
|
|
367
487
|
}
|
|
368
488
|
|
|
369
489
|
function handoff() {
|
|
370
|
-
const owner = acquisition
|
|
490
|
+
const { owner, ending } = acquisition
|
|
371
491
|
transition(Phase.Ready)
|
|
372
|
-
|
|
373
|
-
connectTimer = null
|
|
492
|
+
clearAcquisitionTimers()
|
|
374
493
|
acquisition = null
|
|
375
494
|
options.shared.retries = 0
|
|
495
|
+
lifeTimer.start()
|
|
376
496
|
|
|
377
|
-
if (
|
|
378
|
-
endRequested = false
|
|
379
|
-
if (owner.reserve)
|
|
380
|
-
return (queryError(owner, Errors.connection('CONNECTION_ENDED', options)), closing(true))
|
|
497
|
+
if (ending) {
|
|
381
498
|
if (owner.cancelled)
|
|
382
|
-
return closing(
|
|
499
|
+
return closing()
|
|
383
500
|
transition(Phase.Draining)
|
|
384
501
|
return execute(owner)
|
|
385
502
|
}
|
|
386
503
|
|
|
387
|
-
owner.
|
|
388
|
-
? onopen(connection, owner)
|
|
389
|
-
: owner.cancelled
|
|
390
|
-
? onopen(connection)
|
|
391
|
-
: execute(owner)
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
async function cancel({ pid, secret }, resolve, reject) {
|
|
395
|
-
let timeout = null
|
|
396
|
-
try {
|
|
397
|
-
let s = await Promise.resolve(options.socket ? options.socket(options) : new net.Socket())
|
|
398
|
-
const request = b().i32(16).i32(80877102).i32(pid).i32(secret).end(16)
|
|
399
|
-
const watch = x => {
|
|
400
|
-
x.once('error', reject)
|
|
401
|
-
x.on('error', noop)
|
|
402
|
-
x.once('close', () => (clearTimeout(timeout), resolve()))
|
|
403
|
-
}
|
|
404
|
-
const upgrade = () => {
|
|
405
|
-
const raw = s
|
|
406
|
-
const config = tlsConfig(raw)
|
|
407
|
-
raw.removeAllListeners()
|
|
408
|
-
s = tls.connect(config)
|
|
409
|
-
watch(s)
|
|
410
|
-
s.once('secureConnect', () => s.write(request))
|
|
411
|
-
}
|
|
412
|
-
const start = !ssl
|
|
413
|
-
? () => s.write(request)
|
|
414
|
-
: sslnegotiation === 'direct'
|
|
415
|
-
? upgrade
|
|
416
|
-
: () => {
|
|
417
|
-
s.once('data', x => x[0] === 83 || ssl !== 'prefer' ? upgrade() : s.write(request))
|
|
418
|
-
s.write(SSLRequest)
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
watch(s)
|
|
422
|
-
options.connect_timeout && (timeout = setTimeout(() => s.destroy(), options.connect_timeout * 1000))
|
|
423
|
-
|
|
424
|
-
if (options.socket)
|
|
425
|
-
return start()
|
|
426
|
-
|
|
427
|
-
s.once('connect', start)
|
|
428
|
-
|
|
429
|
-
if (options.path)
|
|
430
|
-
return s.connect(options.path)
|
|
431
|
-
|
|
432
|
-
s.ssl = ssl
|
|
433
|
-
s.connect(port[0], host[0])
|
|
434
|
-
s.host = host[0]
|
|
435
|
-
s.port = port[0]
|
|
436
|
-
} catch (error) {
|
|
437
|
-
reject(error)
|
|
438
|
-
}
|
|
504
|
+
onopen(connection, owner.cancelled ? undefined : owner)
|
|
439
505
|
}
|
|
440
506
|
|
|
441
507
|
function execute(q) {
|
|
442
|
-
|
|
443
|
-
|
|
508
|
+
return dispatch('execute', q)
|
|
509
|
+
}
|
|
444
510
|
|
|
511
|
+
function submit(q) {
|
|
445
512
|
if (stream)
|
|
446
513
|
return queryError(q, Errors.generic('COPY_IN_PROGRESS', 'You cannot execute queries during copy'))
|
|
447
514
|
|
|
448
515
|
if (q.cancelled)
|
|
449
|
-
return
|
|
516
|
+
return true
|
|
517
|
+
|
|
518
|
+
q.owner === undefined && (q.owner = connection.owner)
|
|
450
519
|
|
|
451
520
|
try {
|
|
452
521
|
q.state = backend
|
|
@@ -542,24 +611,6 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
|
|
|
542
611
|
return x
|
|
543
612
|
}
|
|
544
613
|
|
|
545
|
-
function resetSocketState(err) {
|
|
546
|
-
clearImmediate(nextWriteTimer)
|
|
547
|
-
stream && (stream.destroy(err), stream = null)
|
|
548
|
-
final && (final(err), final = null)
|
|
549
|
-
query && queryError(query, err)
|
|
550
|
-
while (sent.length)
|
|
551
|
-
queryError(sent.shift(), err)
|
|
552
|
-
query = results = errorResponse = chunk = nextWriteTimer = null
|
|
553
|
-
result = new Result()
|
|
554
|
-
rows = 0
|
|
555
|
-
nonce = serverSignature = null
|
|
556
|
-
}
|
|
557
|
-
|
|
558
|
-
/* c8 ignore next 3 */
|
|
559
|
-
function drain() {
|
|
560
|
-
phase === Phase.Ready && !query && !held() && onopen(connection)
|
|
561
|
-
}
|
|
562
|
-
|
|
563
614
|
function data(x) {
|
|
564
615
|
if (incomings) {
|
|
565
616
|
incomings.push(x)
|
|
@@ -587,7 +638,7 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
|
|
|
587
638
|
handle(incoming.subarray(0, length + 1))
|
|
588
639
|
} catch (e) {
|
|
589
640
|
query && (query.cursorFn || query.describeFirst) && write(Sync)
|
|
590
|
-
|
|
641
|
+
dispatch('protocolError', e)
|
|
591
642
|
}
|
|
592
643
|
if (socket !== source)
|
|
593
644
|
return
|
|
@@ -597,22 +648,15 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
|
|
|
597
648
|
}
|
|
598
649
|
}
|
|
599
650
|
|
|
600
|
-
function
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
if (phase === Phase.Ready || phase === Phase.Draining)
|
|
605
|
-
socketFailed(err)
|
|
651
|
+
function lost(err, counted = true) {
|
|
652
|
+
counted && options.shared.retries++
|
|
653
|
+
inheritedBackoff = { at: performance.now(), delay: backoffMs() }
|
|
654
|
+
enterClosed(err, err.code === 'CONNECTION_CLOSED' ? err : closedBy(err))
|
|
606
655
|
}
|
|
607
656
|
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
queryError(sent.shift(), err)
|
|
612
|
-
query = null
|
|
613
|
-
options.shared.retries++
|
|
614
|
-
inheritedBackoff = { at: performance.now(), delay: backoffMs() }
|
|
615
|
-
closing(false)
|
|
657
|
+
// CONNECTION_CLOSED stays the code callers retry on; the FATAL, socket error or target_session_attrs mismatch is its cause.
|
|
658
|
+
function closedBy(cause) {
|
|
659
|
+
return Errors.connection('CONNECTION_CLOSED', options, socket, cause)
|
|
616
660
|
}
|
|
617
661
|
|
|
618
662
|
function errored(err) {
|
|
@@ -637,68 +681,16 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
|
|
|
637
681
|
query.reject(err)
|
|
638
682
|
}
|
|
639
683
|
|
|
640
|
-
function
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
const done = new Promise(resolve => endWaiters.push(resolve))
|
|
645
|
-
|
|
646
|
-
if (phase === Phase.Closing || phase === Phase.Draining)
|
|
647
|
-
return done
|
|
648
|
-
|
|
649
|
-
if (phase === Phase.Ready) {
|
|
650
|
-
!held() && onend(connection)
|
|
651
|
-
idle() && !held()
|
|
652
|
-
? closing(true)
|
|
653
|
-
: transition(Phase.Draining)
|
|
654
|
-
return done
|
|
655
|
-
}
|
|
656
|
-
|
|
657
|
-
if (acquisition.owner.reserve)
|
|
684
|
+
function endAcquisition(err) {
|
|
685
|
+
const a = acquisition
|
|
686
|
+
if (a.owner.reserve)
|
|
658
687
|
enterClosed(Errors.connection('CONNECTION_ENDED', options))
|
|
659
|
-
else if (
|
|
660
|
-
enterClosed(
|
|
661
|
-
else if (!
|
|
662
|
-
(
|
|
663
|
-
|
|
664
|
-
return done
|
|
665
|
-
}
|
|
666
|
-
|
|
667
|
-
function terminate() {
|
|
668
|
-
if (phase === Phase.Closed)
|
|
669
|
-
return
|
|
670
|
-
|
|
671
|
-
const destroyed = Errors.connection('CONNECTION_DESTROYED', options, socket)
|
|
672
|
-
if (phase === Phase.Closing)
|
|
673
|
-
return socket.destroy()
|
|
674
|
-
|
|
675
|
-
if (phase !== Phase.Ready && phase !== Phase.Draining)
|
|
676
|
-
return enterClosed(destroyed)
|
|
677
|
-
|
|
678
|
-
const inflight = !idle()
|
|
679
|
-
resetSocketState(destroyed)
|
|
680
|
-
enterClosed(inflight ? destroyed : undefined)
|
|
681
|
-
}
|
|
682
|
-
|
|
683
|
-
function release() {
|
|
684
|
-
phase === Phase.Ready
|
|
685
|
-
? onopen(connection)
|
|
686
|
-
: phase === Phase.Draining && idle() && drained()
|
|
688
|
+
else if (err)
|
|
689
|
+
enterClosed(err)
|
|
690
|
+
else if (!a.ending)
|
|
691
|
+
(a.ending = true, onend(connection))
|
|
687
692
|
}
|
|
688
693
|
|
|
689
|
-
function closed(hadError) {
|
|
690
|
-
const err = Errors.connection('CONNECTION_CLOSED', options, socket)
|
|
691
|
-
if (starting())
|
|
692
|
-
return fail(errorResponse || err, true)
|
|
693
|
-
|
|
694
|
-
if (!inheritedBackoff && (phase !== Phase.Closing || hadError)) {
|
|
695
|
-
hadError && options.shared.retries++
|
|
696
|
-
inheritedBackoff = { at: performance.now(), delay: backoffMs() }
|
|
697
|
-
}
|
|
698
|
-
enterClosed(err)
|
|
699
|
-
}
|
|
700
|
-
|
|
701
|
-
|
|
702
694
|
/* Handlers */
|
|
703
695
|
function handle(xs, x = xs[0]) {
|
|
704
696
|
(
|
|
@@ -776,14 +768,14 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
|
|
|
776
768
|
}
|
|
777
769
|
}
|
|
778
770
|
|
|
779
|
-
function ReadyForQuery(
|
|
771
|
+
function ReadyForQuery() {
|
|
780
772
|
if (query) {
|
|
781
773
|
if (errorResponse) {
|
|
782
774
|
if (query.initialization)
|
|
783
|
-
return
|
|
775
|
+
return dispatch('initFailed', errorResponse)
|
|
784
776
|
query.retried
|
|
785
777
|
? errored(query.retried)
|
|
786
|
-
: query.prepared && retryRoutines.has(errorResponse.routine)
|
|
778
|
+
: query.prepared && retryRoutines.has(errorResponse.routine) && query.owner === connection.owner
|
|
787
779
|
? retry(query, errorResponse)
|
|
788
780
|
: errored(errorResponse)
|
|
789
781
|
} else {
|
|
@@ -797,37 +789,26 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
|
|
|
797
789
|
query = results = errorResponse = null
|
|
798
790
|
result = new Result()
|
|
799
791
|
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
if (phase === Phase.Closing)
|
|
804
|
-
return
|
|
805
|
-
|
|
806
|
-
while (sent.length && (query = sent.shift()) && (query.active = true, query.cancelled))
|
|
807
|
-
Connection(options).cancel(query.state, query.cancelled.resolve, query.cancelled.reject)
|
|
792
|
+
dispatch('ready')
|
|
793
|
+
}
|
|
808
794
|
|
|
809
|
-
|
|
810
|
-
|
|
795
|
+
function pipelined() {
|
|
796
|
+
query = sent.length ? sent.shift() : null
|
|
797
|
+
if (query) {
|
|
798
|
+
query.active = true
|
|
799
|
+
query.cancelled && cancelRequest(options, query.state).then(query.cancelled.resolve, query.cancelled.reject)
|
|
800
|
+
}
|
|
811
801
|
|
|
812
|
-
|
|
813
|
-
? !connection.reserved.release && x[5] === 73
|
|
814
|
-
? phase === Phase.Draining
|
|
815
|
-
? drained()
|
|
816
|
-
: (connection.reserved = null, onopen(connection))
|
|
817
|
-
: connection.reserved()
|
|
818
|
-
: phase === Phase.Draining
|
|
819
|
-
? drained()
|
|
820
|
-
: onopen(connection)
|
|
802
|
+
return query
|
|
821
803
|
}
|
|
822
804
|
|
|
823
805
|
function initialized() {
|
|
824
806
|
if (target_session_attrs) {
|
|
825
807
|
if (!backendParameters.in_hot_standby || !backendParameters.default_transaction_read_only)
|
|
826
808
|
return fetchState()
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
return
|
|
830
|
-
}
|
|
809
|
+
const reason = mismatchReason(target_session_attrs, backendParameters)
|
|
810
|
+
if (reason)
|
|
811
|
+
return mismatched(reason)
|
|
831
812
|
}
|
|
832
813
|
|
|
833
814
|
if (needsTypes)
|
|
@@ -851,7 +832,7 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
|
|
|
851
832
|
|
|
852
833
|
final && (final(), final = null)
|
|
853
834
|
|
|
854
|
-
if (result.command === 'BEGIN' && max !== 1 && !
|
|
835
|
+
if (result.command === 'BEGIN' && max !== 1 && !query.owner)
|
|
855
836
|
return errored(Errors.generic('UNSAFE_TRANSACTION', 'Only use sql.begin, sql.reserved or max: 1'))
|
|
856
837
|
|
|
857
838
|
if (query.options.simple)
|
|
@@ -929,7 +910,7 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
|
|
|
929
910
|
type === 12 ? SASLFinal :
|
|
930
911
|
type !== 0 ? UnknownAuth :
|
|
931
912
|
noop
|
|
932
|
-
)(x, type, attempt)).catch(err => attempt === generation &&
|
|
913
|
+
)(x, type, attempt)).catch(err => attempt === generation && dispatch('authFailed', err))
|
|
933
914
|
}
|
|
934
915
|
|
|
935
916
|
/* c8 ignore next 5 */
|
|
@@ -1003,7 +984,7 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
|
|
|
1003
984
|
if (x.toString('utf8', 9).split(b.N, 1)[0].slice(2) === serverSignature)
|
|
1004
985
|
return
|
|
1005
986
|
/* c8 ignore next 5 */
|
|
1006
|
-
|
|
987
|
+
dispatch('authFailed', Errors.generic('SASL_SIGNATURE_MISMATCH', 'The server did not return the correct signature'))
|
|
1007
988
|
}
|
|
1008
989
|
|
|
1009
990
|
function Pass() {
|
|
@@ -1037,13 +1018,14 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
|
|
|
1037
1018
|
`, types => types.forEach(({ oid, typarray }) => addArrayType(options, oid, typarray)))
|
|
1038
1019
|
}
|
|
1039
1020
|
|
|
1040
|
-
function
|
|
1021
|
+
function mismatchReason(x, xs) {
|
|
1041
1022
|
return (
|
|
1042
|
-
(x === 'read-write' && xs.default_transaction_read_only === 'on') ||
|
|
1043
|
-
(x === 'read-only' && xs.default_transaction_read_only === 'off') ||
|
|
1044
|
-
(x === 'primary' && xs.in_hot_standby === 'on') ||
|
|
1045
|
-
(x === 'standby' && xs.in_hot_standby === 'off') ||
|
|
1046
|
-
(x === 'prefer-standby' && xs.in_hot_standby === 'off' &&
|
|
1023
|
+
(x === 'read-write' && xs.default_transaction_read_only === 'on' && 'is read-only') ||
|
|
1024
|
+
(x === 'read-only' && xs.default_transaction_read_only === 'off' && 'is read-write') ||
|
|
1025
|
+
(x === 'primary' && xs.in_hot_standby === 'on' && 'is a standby') ||
|
|
1026
|
+
(x === 'standby' && xs.in_hot_standby === 'off' && 'is a primary') ||
|
|
1027
|
+
(x === 'prefer-standby' && xs.in_hot_standby === 'off' && standbyPass() && 'is a primary') ||
|
|
1028
|
+
null
|
|
1047
1029
|
)
|
|
1048
1030
|
}
|
|
1049
1031
|
|
|
@@ -1058,7 +1040,6 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
|
|
|
1058
1040
|
}
|
|
1059
1041
|
|
|
1060
1042
|
function initialize(string, resolve, simple = false) {
|
|
1061
|
-
transition(Phase.Initializing)
|
|
1062
1043
|
const attempt = generation
|
|
1063
1044
|
const q = new Query([string], [], q => attempt === generation
|
|
1064
1045
|
? execute(q)
|
|
@@ -1072,10 +1053,7 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
|
|
|
1072
1053
|
(query.cursorFn || query.describeFirst) && write(Sync)
|
|
1073
1054
|
errorResponse = Errors.postgres(parseError(x))
|
|
1074
1055
|
} else {
|
|
1075
|
-
|
|
1076
|
-
phase === Phase.Ready || phase === Phase.Draining
|
|
1077
|
-
? socketFailed(err)
|
|
1078
|
-
: phase === Phase.Authenticating && fail(err)
|
|
1056
|
+
dispatch('serverError', Errors.postgres(parseError(x)))
|
|
1079
1057
|
}
|
|
1080
1058
|
}
|
|
1081
1059
|
|
|
@@ -1307,21 +1285,28 @@ function xor(a, b) {
|
|
|
1307
1285
|
function timer(fn, seconds) {
|
|
1308
1286
|
seconds = typeof seconds === 'function' ? seconds() : seconds
|
|
1309
1287
|
if (!seconds)
|
|
1310
|
-
return { cancel: noop, start: noop }
|
|
1288
|
+
return { cancel: noop, start: noop, expired: () => false }
|
|
1311
1289
|
|
|
1312
1290
|
let timer
|
|
1291
|
+
, due = null
|
|
1313
1292
|
return {
|
|
1314
1293
|
cancel() {
|
|
1315
1294
|
timer && (clearTimeout(timer), timer = null)
|
|
1295
|
+
due = null
|
|
1316
1296
|
},
|
|
1317
1297
|
start(...args) {
|
|
1318
1298
|
timer && clearTimeout(timer)
|
|
1319
|
-
|
|
1299
|
+
due = Date.now() + seconds * 1000
|
|
1300
|
+
timer = clampedTimeout(() => done(args), seconds * 1000)
|
|
1301
|
+
},
|
|
1302
|
+
expired() {
|
|
1303
|
+
return due !== null && Date.now() >= due
|
|
1320
1304
|
}
|
|
1321
1305
|
}
|
|
1322
1306
|
|
|
1323
1307
|
function done(args) {
|
|
1324
1308
|
timer = null
|
|
1309
|
+
due = null
|
|
1325
1310
|
fn.apply(null, args)
|
|
1326
1311
|
}
|
|
1327
1312
|
}
|