@toa.io/core 1.0.0-alpha.239 → 1.0.0-alpha.243

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@toa.io/core",
3
- "version": "1.0.0-alpha.239",
3
+ "version": "1.0.0-alpha.243",
4
4
  "description": "Toa Core",
5
5
  "author": "temich <tema.gurtovoy@gmail.com>",
6
6
  "homepage": "https://github.com/toa-io/toa#readme",
@@ -27,5 +27,5 @@
27
27
  "openspan": "1.0.0-alpha.173",
28
28
  "uuid": "11.1.1"
29
29
  },
30
- "gitHead": "35935a713890e680fccbd23f77570da2bc12fd64"
30
+ "gitHead": "743e8060d35ca750f70487c04aef9698591c5edd"
31
31
  }
package/src/connector.js CHANGED
@@ -110,7 +110,11 @@ class Connector {
110
110
  * @returns {Promise<void>}
111
111
  */
112
112
  async disconnect (interrupt) {
113
- if (interrupt !== true) await this.#connecting
113
+ // a connector that has not finished connecting has nothing to close, and
114
+ // awaiting a connection that may never settle outlives any grace period
115
+ const pending = interrupt === true || this.connected === false
116
+
117
+ if (!pending) await this.#connecting
114
118
 
115
119
  if (this.#disconnecting) return this.#disconnecting
116
120
 
@@ -131,7 +135,7 @@ class Connector {
131
135
  console.warn(`Connector ${this.id} still disconnecting (${delay})`)
132
136
  }, DELAY)
133
137
 
134
- if (interrupt !== true) await this.close()
138
+ if (!pending) await this.close()
135
139
 
136
140
  clearInterval(interval)
137
141
 
package/src/discovery.js CHANGED
@@ -25,17 +25,26 @@ class Discovery extends Connector {
25
25
  this.depends(this.#lookups[id])
26
26
  }
27
27
 
28
- const warning = () => console.warn(`Waiting for lookup response`, { component: id })
29
- const timeout = setTimeout(warning, TIMEOUT)
28
+ const since = Date.now()
29
+
30
+ // the wait is unbounded by design, as a dependency may still be starting,
31
+ // so repeating is the only way a component stuck on one stays visible
32
+ const warning = setInterval(() => {
33
+ const waiting = Math.round((Date.now() - since) / 1000)
34
+
35
+ console.warn('Waiting for lookup response', { component: id, waiting })
36
+ }, INTERVAL)
37
+
38
+ warning.unref()
30
39
 
31
40
  const output = await this.#lookups[id].invoke()
32
41
 
33
- clearTimeout(timeout)
42
+ clearInterval(warning)
34
43
 
35
44
  return output
36
45
  }
37
46
  }
38
47
 
39
- const TIMEOUT = 5000
48
+ const INTERVAL = 5000
40
49
 
41
50
  exports.Discovery = Discovery
@@ -36,5 +36,13 @@ class FailingConnector extends Connector {
36
36
  }
37
37
  }
38
38
 
39
+ /** Stands for a connector waiting for something that never arrives. */
40
+ class StuckConnector extends Connector {
41
+ async open () {
42
+ return new Promise(() => undefined)
43
+ }
44
+ }
45
+
39
46
  exports.TestConnector = TestConnector
40
47
  exports.FailingConnector = FailingConnector
48
+ exports.StuckConnector = StuckConnector
@@ -1,5 +1,7 @@
1
1
  'use strict'
2
2
 
3
+ const { timeout } = require('@toa.io/generic')
4
+
3
5
  const fixtures = require('./connector.fixtures')
4
6
 
5
7
  let sequence
@@ -105,6 +107,7 @@ describe('dependencies', () => {
105
107
  a.depends(b).depends(c)
106
108
  b.depends(d)
107
109
 
110
+ await a.connect()
108
111
  await a.disconnect()
109
112
 
110
113
  expect(sequence.indexOf('-a')).toBeLessThan(sequence.indexOf('-b'))
@@ -146,6 +149,23 @@ describe('dependencies', () => {
146
149
  expect(() => a.depends({})).toThrow()
147
150
  })
148
151
 
152
+ it('should disconnect while still connecting', async () => {
153
+ const stuck = new fixtures.StuckConnector()
154
+
155
+ a.depends(stuck).depends(b)
156
+
157
+ void a.connect()
158
+
159
+ while (b.connected !== true) await timeout(1)
160
+
161
+ // `a` never connects, and disconnecting must not wait for it to
162
+ expect(a.connected).toStrictEqual(false)
163
+
164
+ await a.disconnect()
165
+
166
+ expect(sequence).toEqual(['+b', '-b', '*b', '*a'])
167
+ })
168
+
149
169
  describe('errors', () => {
150
170
  let f
151
171
 
@@ -0,0 +1,56 @@
1
+ 'use strict'
2
+
3
+ const { console } = require('openspan')
4
+ const { Connector } = require('../src/connector')
5
+ const { Discovery } = require('../src/discovery')
6
+
7
+ class Lookup extends Connector {
8
+ invoke = jest.fn(async () => ({ operations: {} }))
9
+ }
10
+
11
+ /** @type {Lookup} */
12
+ let lookup
13
+
14
+ /** @type {Discovery} */
15
+ let discovery
16
+
17
+ /** @type {jest.SpyInstance} */
18
+ let warn
19
+
20
+ const locator = { id: 'dummies.one' }
21
+
22
+ beforeEach(async () => {
23
+ jest.useFakeTimers()
24
+
25
+ warn = jest.spyOn(console, 'warn').mockImplementation(() => undefined)
26
+ lookup = new Lookup()
27
+ discovery = new Discovery(async () => lookup)
28
+
29
+ await discovery.connect()
30
+ })
31
+
32
+ afterEach(() => {
33
+ warn.mockRestore()
34
+ jest.useRealTimers()
35
+ })
36
+
37
+ it('should not warn if a lookup is answered', async () => {
38
+ await discovery.lookup(locator)
39
+
40
+ jest.advanceTimersByTime(60_000)
41
+
42
+ expect(warn).not.toHaveBeenCalled()
43
+ })
44
+
45
+ it('should keep warning while a lookup is unanswered', async () => {
46
+ lookup.invoke.mockImplementation(() => new Promise(() => undefined))
47
+
48
+ void discovery.lookup(locator)
49
+
50
+ await jest.advanceTimersByTimeAsync(12_000)
51
+
52
+ expect(warn).toHaveBeenCalledTimes(2)
53
+
54
+ expect(warn).toHaveBeenLastCalledWith('Waiting for lookup response',
55
+ { component: locator.id, waiting: 10 })
56
+ })