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