@bradensbay/globals-core 0.2.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 +69 -0
- package/dist/src/allocator.d.ts +78 -0
- package/dist/src/allocator.d.ts.map +1 -0
- package/dist/src/allocator.js +211 -0
- package/dist/src/allocator.js.map +1 -0
- package/dist/src/arena.d.ts +81 -0
- package/dist/src/arena.d.ts.map +1 -0
- package/dist/src/arena.js +224 -0
- package/dist/src/arena.js.map +1 -0
- package/dist/src/checksum.d.ts +5 -0
- package/dist/src/checksum.d.ts.map +1 -0
- package/dist/src/checksum.js +35 -0
- package/dist/src/checksum.js.map +1 -0
- package/dist/src/draft.d.ts +48 -0
- package/dist/src/draft.d.ts.map +1 -0
- package/dist/src/draft.js +427 -0
- package/dist/src/draft.js.map +1 -0
- package/dist/src/errors.d.ts +56 -0
- package/dist/src/errors.d.ts.map +1 -0
- package/dist/src/errors.js +69 -0
- package/dist/src/errors.js.map +1 -0
- package/dist/src/external.d.ts +52 -0
- package/dist/src/external.d.ts.map +1 -0
- package/dist/src/external.js +68 -0
- package/dist/src/external.js.map +1 -0
- package/dist/src/hamt.d.ts +87 -0
- package/dist/src/hamt.d.ts.map +1 -0
- package/dist/src/hamt.js +643 -0
- package/dist/src/hamt.js.map +1 -0
- package/dist/src/history.d.ts +45 -0
- package/dist/src/history.d.ts.map +1 -0
- package/dist/src/history.js +49 -0
- package/dist/src/history.js.map +1 -0
- package/dist/src/index.d.ts +55 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +38 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/inspect.d.ts +74 -0
- package/dist/src/inspect.d.ts.map +1 -0
- package/dist/src/inspect.js +127 -0
- package/dist/src/inspect.js.map +1 -0
- package/dist/src/layout.d.ts +130 -0
- package/dist/src/layout.d.ts.map +1 -0
- package/dist/src/layout.js +149 -0
- package/dist/src/layout.js.map +1 -0
- package/dist/src/liveness.d.ts +48 -0
- package/dist/src/liveness.d.ts.map +1 -0
- package/dist/src/liveness.js +92 -0
- package/dist/src/liveness.js.map +1 -0
- package/dist/src/owner.d.ts +130 -0
- package/dist/src/owner.d.ts.map +1 -0
- package/dist/src/owner.js +368 -0
- package/dist/src/owner.js.map +1 -0
- package/dist/src/reader.d.ts +122 -0
- package/dist/src/reader.d.ts.map +1 -0
- package/dist/src/reader.js +325 -0
- package/dist/src/reader.js.map +1 -0
- package/dist/src/readers.d.ts +60 -0
- package/dist/src/readers.d.ts.map +1 -0
- package/dist/src/readers.js +122 -0
- package/dist/src/readers.js.map +1 -0
- package/dist/src/retained.d.ts +39 -0
- package/dist/src/retained.d.ts.map +1 -0
- package/dist/src/retained.js +99 -0
- package/dist/src/retained.js.map +1 -0
- package/dist/src/schema.d.ts +82 -0
- package/dist/src/schema.d.ts.map +1 -0
- package/dist/src/schema.js +18 -0
- package/dist/src/schema.js.map +1 -0
- package/dist/src/store.d.ts +83 -0
- package/dist/src/store.d.ts.map +1 -0
- package/dist/src/store.js +127 -0
- package/dist/src/store.js.map +1 -0
- package/dist/src/strings.d.ts +37 -0
- package/dist/src/strings.d.ts.map +1 -0
- package/dist/src/strings.js +144 -0
- package/dist/src/strings.js.map +1 -0
- package/dist/src/tags.d.ts +47 -0
- package/dist/src/tags.d.ts.map +1 -0
- package/dist/src/tags.js +53 -0
- package/dist/src/tags.js.map +1 -0
- package/dist/src/values.d.ts +90 -0
- package/dist/src/values.d.ts.map +1 -0
- package/dist/src/values.js +458 -0
- package/dist/src/values.js.map +1 -0
- package/dist/src/vector.d.ts +64 -0
- package/dist/src/vector.d.ts.map +1 -0
- package/dist/src/vector.js +387 -0
- package/dist/src/vector.js.map +1 -0
- package/dist/src/verify.d.ts +38 -0
- package/dist/src/verify.d.ts.map +1 -0
- package/dist/src/verify.js +179 -0
- package/dist/src/verify.js.map +1 -0
- package/dist/src/view.d.ts +66 -0
- package/dist/src/view.d.ts.map +1 -0
- package/dist/src/view.js +278 -0
- package/dist/src/view.js.map +1 -0
- package/package.json +28 -0
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The shared memory layout.
|
|
3
|
+
*
|
|
4
|
+
* Everything here is a fixed offset agreed between the owner and every reader. A change to
|
|
5
|
+
* any of it is a breaking change to the wire format and requires a LAYOUT_VERSION bump,
|
|
6
|
+
* because an owner and a reader from different builds may map the same buffer.
|
|
7
|
+
*
|
|
8
|
+
* Byte map:
|
|
9
|
+
*
|
|
10
|
+
* 0 header, HEADER_BYTES
|
|
11
|
+
* HEADER_BYTES reader table, maxReaders * READER_SLOT_BYTES
|
|
12
|
+
* ... retained version ring, retainedCap * RETAINED_SLOT_BYTES
|
|
13
|
+
* ... arena, grows upward from a bump pointer
|
|
14
|
+
*/
|
|
15
|
+
/** "GLOB" in ASCII. A buffer that does not start with this is not one of ours. */
|
|
16
|
+
export const MAGIC = 0x474c4f42;
|
|
17
|
+
/** Bump on any change to the byte map, the block header, or the tag meanings. */
|
|
18
|
+
export const LAYOUT_VERSION = 1;
|
|
19
|
+
export const WORD = 4;
|
|
20
|
+
export const SLOT_BYTES = 8;
|
|
21
|
+
/** Every allocation starts on an eight byte boundary so float64 stores are aligned. */
|
|
22
|
+
export const ALIGNMENT = 8;
|
|
23
|
+
// ---------------------------------------------------------------------------
|
|
24
|
+
// Header, in 32 bit words
|
|
25
|
+
// ---------------------------------------------------------------------------
|
|
26
|
+
export const Header = {
|
|
27
|
+
Magic: 0,
|
|
28
|
+
LayoutVersion: 1,
|
|
29
|
+
/** Total byte length the owner believes the buffer has. Updated on grow. */
|
|
30
|
+
CapacityBytes: 2,
|
|
31
|
+
/** Guards the configuration words, so a reader can detect a stomped header. */
|
|
32
|
+
ConfigChecksum: 3,
|
|
33
|
+
/**
|
|
34
|
+
* Seqlock over the root publication. Even means stable, odd means a commit is in
|
|
35
|
+
* progress. Readers retry rather than block.
|
|
36
|
+
*/
|
|
37
|
+
Sequence: 4,
|
|
38
|
+
RootTag: 5,
|
|
39
|
+
RootPayload: 6,
|
|
40
|
+
/** Monotonic. Doubles as the epoch a reader pins. */
|
|
41
|
+
VersionId: 7,
|
|
42
|
+
/** Byte offset of the next unallocated byte. */
|
|
43
|
+
BumpPointer: 8,
|
|
44
|
+
MaxReaders: 9,
|
|
45
|
+
ReaderTableOffset: 10,
|
|
46
|
+
RetainedRingOffset: 11,
|
|
47
|
+
RetainedCapacity: 12,
|
|
48
|
+
ArenaOffset: 13,
|
|
49
|
+
/** Oldest version still retained. A snapshot below this is stale. */
|
|
50
|
+
ReclaimFloor: 14,
|
|
51
|
+
/** Verification mode, see VerifyMode. */
|
|
52
|
+
Flags: 15,
|
|
53
|
+
/** Bumped by the owner on every liveness tick so readers can detect a dead owner. */
|
|
54
|
+
OwnerHeartbeat: 16,
|
|
55
|
+
/** Bumped when a new owner adopts the buffer, so stale readers fail closed. */
|
|
56
|
+
OwnerGeneration: 17,
|
|
57
|
+
StatCommits: 18,
|
|
58
|
+
StatBytesAllocated: 19,
|
|
59
|
+
StatBytesFreed: 20,
|
|
60
|
+
StatBytesLive: 21,
|
|
61
|
+
StatForcedAdvances: 22,
|
|
62
|
+
/** Checksum over the header and the current root, when verification is on. */
|
|
63
|
+
RootChecksum: 23,
|
|
64
|
+
};
|
|
65
|
+
export const HEADER_WORDS = 32;
|
|
66
|
+
export const HEADER_BYTES = HEADER_WORDS * WORD;
|
|
67
|
+
/** Words the ConfigChecksum covers. These never change after creation. */
|
|
68
|
+
export const CONFIG_WORDS = [
|
|
69
|
+
Header.Magic,
|
|
70
|
+
Header.LayoutVersion,
|
|
71
|
+
Header.MaxReaders,
|
|
72
|
+
Header.ReaderTableOffset,
|
|
73
|
+
Header.RetainedRingOffset,
|
|
74
|
+
Header.RetainedCapacity,
|
|
75
|
+
Header.ArenaOffset,
|
|
76
|
+
];
|
|
77
|
+
// ---------------------------------------------------------------------------
|
|
78
|
+
// Reader table
|
|
79
|
+
// ---------------------------------------------------------------------------
|
|
80
|
+
export const ReaderSlot = {
|
|
81
|
+
/** 0 free, 1 claimed. Claimed with compareExchange so two readers cannot share a slot. */
|
|
82
|
+
State: 0,
|
|
83
|
+
/** Bumped on each claim. A snapshot from an earlier generation is not valid. */
|
|
84
|
+
Generation: 1,
|
|
85
|
+
/** The version this reader is pinned to. Zero means it is not reading. */
|
|
86
|
+
Epoch: 2,
|
|
87
|
+
/** Monotonic counter the reader bumps. A stalled counter means a dead or frozen reader. */
|
|
88
|
+
Heartbeat: 3,
|
|
89
|
+
};
|
|
90
|
+
export const READER_SLOT_WORDS = 4;
|
|
91
|
+
export const READER_SLOT_BYTES = READER_SLOT_WORDS * WORD;
|
|
92
|
+
export const SLOT_FREE = 0;
|
|
93
|
+
export const SLOT_CLAIMED = 1;
|
|
94
|
+
// ---------------------------------------------------------------------------
|
|
95
|
+
// Retained version ring
|
|
96
|
+
// ---------------------------------------------------------------------------
|
|
97
|
+
export const RetainedSlot = {
|
|
98
|
+
VersionId: 0,
|
|
99
|
+
RootTag: 1,
|
|
100
|
+
RootPayload: 2,
|
|
101
|
+
/** See RetainedState. */
|
|
102
|
+
State: 3,
|
|
103
|
+
};
|
|
104
|
+
export const RETAINED_SLOT_WORDS = 4;
|
|
105
|
+
export const RETAINED_SLOT_BYTES = RETAINED_SLOT_WORDS * WORD;
|
|
106
|
+
export const RetainedState = {
|
|
107
|
+
Empty: 0,
|
|
108
|
+
/** Readers may pin this version. */
|
|
109
|
+
Live: 1,
|
|
110
|
+
/** Reclaimed normally, every reader had moved past it. */
|
|
111
|
+
Reclaimed: 2,
|
|
112
|
+
/** Reclaimed while a reader was still pinned, because retention hit its cap. */
|
|
113
|
+
ForceReclaimed: 3,
|
|
114
|
+
};
|
|
115
|
+
// ---------------------------------------------------------------------------
|
|
116
|
+
// Allocation block header
|
|
117
|
+
// ---------------------------------------------------------------------------
|
|
118
|
+
/**
|
|
119
|
+
* Every allocation is preceded by two words. The magic makes a wild offset detectable
|
|
120
|
+
* instead of merely wrong, and the size class lets the allocator return the block to the
|
|
121
|
+
* right free list without a side table.
|
|
122
|
+
*/
|
|
123
|
+
export const Block = {
|
|
124
|
+
/** BLOCK_MAGIC in the high 24 bits, size class index in the low 8. */
|
|
125
|
+
Header: -2,
|
|
126
|
+
/** Payload bytes, not counting this header. */
|
|
127
|
+
ByteSize: -1,
|
|
128
|
+
};
|
|
129
|
+
export const BLOCK_HEADER_BYTES = 8;
|
|
130
|
+
export const BLOCK_MAGIC = 0x424c4b; // "BLK"
|
|
131
|
+
export const BLOCK_MAGIC_SHIFT = 8;
|
|
132
|
+
export const BLOCK_CLASS_MASK = 0xff;
|
|
133
|
+
/** Size class 255 means the block was allocated at an exact size and has no free list. */
|
|
134
|
+
export const SIZE_CLASS_EXACT = 0xff;
|
|
135
|
+
export const VerifyMode = {
|
|
136
|
+
Off: 0,
|
|
137
|
+
Header: 1,
|
|
138
|
+
Full: 2,
|
|
139
|
+
};
|
|
140
|
+
export function computeGeometry(maxReaders, retainedCapacity) {
|
|
141
|
+
const readerTableOffset = HEADER_BYTES;
|
|
142
|
+
const retainedRingOffset = readerTableOffset + maxReaders * READER_SLOT_BYTES;
|
|
143
|
+
const arenaOffset = align(retainedRingOffset + retainedCapacity * RETAINED_SLOT_BYTES);
|
|
144
|
+
return { maxReaders, retainedCapacity, readerTableOffset, retainedRingOffset, arenaOffset };
|
|
145
|
+
}
|
|
146
|
+
export function align(bytes) {
|
|
147
|
+
return (bytes + (ALIGNMENT - 1)) & ~(ALIGNMENT - 1);
|
|
148
|
+
}
|
|
149
|
+
//# sourceMappingURL=layout.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"layout.js","sourceRoot":"","sources":["../../src/layout.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,kFAAkF;AAClF,MAAM,CAAC,MAAM,KAAK,GAAG,UAAU,CAAC;AAEhC,iFAAiF;AACjF,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,CAAC;AAEhC,MAAM,CAAC,MAAM,IAAI,GAAG,CAAC,CAAC;AACtB,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,CAAC;AAC5B,uFAAuF;AACvF,MAAM,CAAC,MAAM,SAAS,GAAG,CAAC,CAAC;AAE3B,8EAA8E;AAC9E,0BAA0B;AAC1B,8EAA8E;AAE9E,MAAM,CAAC,MAAM,MAAM,GAAG;IACpB,KAAK,EAAE,CAAC;IACR,aAAa,EAAE,CAAC;IAChB,4EAA4E;IAC5E,aAAa,EAAE,CAAC;IAChB,+EAA+E;IAC/E,cAAc,EAAE,CAAC;IACjB;;;OAGG;IACH,QAAQ,EAAE,CAAC;IACX,OAAO,EAAE,CAAC;IACV,WAAW,EAAE,CAAC;IACd,qDAAqD;IACrD,SAAS,EAAE,CAAC;IACZ,gDAAgD;IAChD,WAAW,EAAE,CAAC;IACd,UAAU,EAAE,CAAC;IACb,iBAAiB,EAAE,EAAE;IACrB,kBAAkB,EAAE,EAAE;IACtB,gBAAgB,EAAE,EAAE;IACpB,WAAW,EAAE,EAAE;IACf,qEAAqE;IACrE,YAAY,EAAE,EAAE;IAChB,yCAAyC;IACzC,KAAK,EAAE,EAAE;IACT,qFAAqF;IACrF,cAAc,EAAE,EAAE;IAClB,+EAA+E;IAC/E,eAAe,EAAE,EAAE;IACnB,WAAW,EAAE,EAAE;IACf,kBAAkB,EAAE,EAAE;IACtB,cAAc,EAAE,EAAE;IAClB,aAAa,EAAE,EAAE;IACjB,kBAAkB,EAAE,EAAE;IACtB,8EAA8E;IAC9E,YAAY,EAAE,EAAE;CACR,CAAC;AAEX,MAAM,CAAC,MAAM,YAAY,GAAG,EAAE,CAAC;AAC/B,MAAM,CAAC,MAAM,YAAY,GAAG,YAAY,GAAG,IAAI,CAAC;AAEhD,0EAA0E;AAC1E,MAAM,CAAC,MAAM,YAAY,GAAG;IAC1B,MAAM,CAAC,KAAK;IACZ,MAAM,CAAC,aAAa;IACpB,MAAM,CAAC,UAAU;IACjB,MAAM,CAAC,iBAAiB;IACxB,MAAM,CAAC,kBAAkB;IACzB,MAAM,CAAC,gBAAgB;IACvB,MAAM,CAAC,WAAW;CACV,CAAC;AAEX,8EAA8E;AAC9E,eAAe;AACf,8EAA8E;AAE9E,MAAM,CAAC,MAAM,UAAU,GAAG;IACxB,0FAA0F;IAC1F,KAAK,EAAE,CAAC;IACR,gFAAgF;IAChF,UAAU,EAAE,CAAC;IACb,0EAA0E;IAC1E,KAAK,EAAE,CAAC;IACR,2FAA2F;IAC3F,SAAS,EAAE,CAAC;CACJ,CAAC;AAEX,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,CAAC;AACnC,MAAM,CAAC,MAAM,iBAAiB,GAAG,iBAAiB,GAAG,IAAI,CAAC;AAE1D,MAAM,CAAC,MAAM,SAAS,GAAG,CAAC,CAAC;AAC3B,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,CAAC;AAE9B,8EAA8E;AAC9E,wBAAwB;AACxB,8EAA8E;AAE9E,MAAM,CAAC,MAAM,YAAY,GAAG;IAC1B,SAAS,EAAE,CAAC;IACZ,OAAO,EAAE,CAAC;IACV,WAAW,EAAE,CAAC;IACd,yBAAyB;IACzB,KAAK,EAAE,CAAC;CACA,CAAC;AAEX,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,CAAC;AACrC,MAAM,CAAC,MAAM,mBAAmB,GAAG,mBAAmB,GAAG,IAAI,CAAC;AAE9D,MAAM,CAAC,MAAM,aAAa,GAAG;IAC3B,KAAK,EAAE,CAAC;IACR,oCAAoC;IACpC,IAAI,EAAE,CAAC;IACP,0DAA0D;IAC1D,SAAS,EAAE,CAAC;IACZ,gFAAgF;IAChF,cAAc,EAAE,CAAC;CACT,CAAC;AAEX,8EAA8E;AAC9E,0BAA0B;AAC1B,8EAA8E;AAE9E;;;;GAIG;AACH,MAAM,CAAC,MAAM,KAAK,GAAG;IACnB,sEAAsE;IACtE,MAAM,EAAE,CAAC,CAAC;IACV,+CAA+C;IAC/C,QAAQ,EAAE,CAAC,CAAC;CACJ,CAAC;AAEX,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,CAAC;AACpC,MAAM,CAAC,MAAM,WAAW,GAAG,QAAQ,CAAC,CAAC,QAAQ;AAC7C,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,CAAC;AACnC,MAAM,CAAC,MAAM,gBAAgB,GAAG,IAAI,CAAC;AACrC,0FAA0F;AAC1F,MAAM,CAAC,MAAM,gBAAgB,GAAG,IAAI,CAAC;AAErC,MAAM,CAAC,MAAM,UAAU,GAAG;IACxB,GAAG,EAAE,CAAC;IACN,MAAM,EAAE,CAAC;IACT,IAAI,EAAE,CAAC;CACC,CAAC;AAgBX,MAAM,UAAU,eAAe,CAAC,UAAkB,EAAE,gBAAwB;IAC1E,MAAM,iBAAiB,GAAG,YAAY,CAAC;IACvC,MAAM,kBAAkB,GAAG,iBAAiB,GAAG,UAAU,GAAG,iBAAiB,CAAC;IAC9E,MAAM,WAAW,GAAG,KAAK,CAAC,kBAAkB,GAAG,gBAAgB,GAAG,mBAAmB,CAAC,CAAC;IACvF,OAAO,EAAE,UAAU,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,WAAW,EAAE,CAAC;AAC9F,CAAC;AAED,MAAM,UAAU,KAAK,CAAC,KAAa;IACjC,OAAO,CAAC,KAAK,GAAG,CAAC,SAAS,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,SAAS,GAAG,CAAC,CAAC,CAAC;AACtD,CAAC"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { ArenaOwner } from "./owner.js";
|
|
2
|
+
/**
|
|
3
|
+
* Liveness detection for readers.
|
|
4
|
+
*
|
|
5
|
+
* A renderer that reloads discards its heap while its slot in the reader table is still
|
|
6
|
+
* claimed and, worse, while its epoch is still pinned. Nothing in the arena notices, so
|
|
7
|
+
* retention grows until the ring wraps. A crashed renderer is the same case without the
|
|
8
|
+
* reload.
|
|
9
|
+
*
|
|
10
|
+
* The detector is deliberately conservative. Reclaiming a slot from a reader that is merely
|
|
11
|
+
* slow costs that reader a StaleSnapshotError and a reacquire, which is recoverable but
|
|
12
|
+
* pointless. So a reader is declared dead only after its heartbeat has failed to move across
|
|
13
|
+
* several consecutive ticks while it was holding a pin.
|
|
14
|
+
*
|
|
15
|
+
* This lives in the core rather than the Electron package because it is the same problem in
|
|
16
|
+
* a worker thread, and because it can be tested without a window manager.
|
|
17
|
+
*/
|
|
18
|
+
export interface LivenessOptions {
|
|
19
|
+
/** How often to sample, in milliseconds. */
|
|
20
|
+
intervalMs?: number;
|
|
21
|
+
/** Consecutive unchanged samples before a pinned reader is declared dead. */
|
|
22
|
+
missesBeforeDead?: number;
|
|
23
|
+
/** Called when a slot is reclaimed, for logging and metrics. */
|
|
24
|
+
onReaped?: (slot: number, detail: {
|
|
25
|
+
epoch: number;
|
|
26
|
+
generation: number;
|
|
27
|
+
}) => void;
|
|
28
|
+
}
|
|
29
|
+
export declare class LivenessMonitor {
|
|
30
|
+
#private;
|
|
31
|
+
constructor(owner: ArenaOwner, options?: LivenessOptions);
|
|
32
|
+
get reapedCount(): number;
|
|
33
|
+
start(): void;
|
|
34
|
+
stop(): void;
|
|
35
|
+
/**
|
|
36
|
+
* One sampling pass. Exposed so tests can drive it without waiting on a timer, and so an
|
|
37
|
+
* integration can run it on a window lifecycle event rather than only on a clock.
|
|
38
|
+
*/
|
|
39
|
+
tick(): void;
|
|
40
|
+
/**
|
|
41
|
+
* Reclaim a slot straight away, for a window the integration knows is gone.
|
|
42
|
+
*
|
|
43
|
+
* A close or crash event is better evidence than any number of missed heartbeats, so an
|
|
44
|
+
* integration that has one should use it rather than waiting for the detector.
|
|
45
|
+
*/
|
|
46
|
+
reapSlot(slot: number): void;
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=liveness.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"liveness.d.ts","sourceRoot":"","sources":["../../src/liveness.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAG7C;;;;;;;;;;;;;;;GAeG;AAEH,MAAM,WAAW,eAAe;IAC9B,4CAA4C;IAC5C,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,6EAA6E;IAC7E,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,gEAAgE;IAChE,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;CAClF;AAaD,qBAAa,eAAe;;IAU1B,YAAY,KAAK,EAAE,UAAU,EAAE,OAAO,GAAE,eAAoB,EAM3D;IAED,IAAI,WAAW,IAAI,MAAM,CAExB;IAED,KAAK,IAAI,IAAI,CAMZ;IAED,IAAI,IAAI,IAAI,CAIX;IAED;;;OAGG;IACH,IAAI,IAAI,IAAI,CAyCX;IAED;;;;;OAKG;IACH,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAK3B;CACF"}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
const DEFAULTS = {
|
|
2
|
+
intervalMs: 1000,
|
|
3
|
+
missesBeforeDead: 5,
|
|
4
|
+
};
|
|
5
|
+
export class LivenessMonitor {
|
|
6
|
+
#owner;
|
|
7
|
+
#table;
|
|
8
|
+
#intervalMs;
|
|
9
|
+
#missesBeforeDead;
|
|
10
|
+
#onReaped;
|
|
11
|
+
#samples = new Map();
|
|
12
|
+
#timer;
|
|
13
|
+
#reaped = 0;
|
|
14
|
+
constructor(owner, options = {}) {
|
|
15
|
+
this.#owner = owner;
|
|
16
|
+
this.#table = owner.readers;
|
|
17
|
+
this.#intervalMs = options.intervalMs ?? DEFAULTS.intervalMs;
|
|
18
|
+
this.#missesBeforeDead = options.missesBeforeDead ?? DEFAULTS.missesBeforeDead;
|
|
19
|
+
this.#onReaped = options.onReaped;
|
|
20
|
+
}
|
|
21
|
+
get reapedCount() {
|
|
22
|
+
return this.#reaped;
|
|
23
|
+
}
|
|
24
|
+
start() {
|
|
25
|
+
if (this.#timer !== undefined)
|
|
26
|
+
return;
|
|
27
|
+
this.#timer = setInterval(() => this.tick(), this.#intervalMs);
|
|
28
|
+
// Do not hold the process open. A liveness timer is a housekeeping detail, and an
|
|
29
|
+
// application that has nothing else to do should be allowed to exit.
|
|
30
|
+
this.#timer.unref?.();
|
|
31
|
+
}
|
|
32
|
+
stop() {
|
|
33
|
+
if (this.#timer === undefined)
|
|
34
|
+
return;
|
|
35
|
+
clearInterval(this.#timer);
|
|
36
|
+
this.#timer = undefined;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* One sampling pass. Exposed so tests can drive it without waiting on a timer, and so an
|
|
40
|
+
* integration can run it on a window lifecycle event rather than only on a clock.
|
|
41
|
+
*/
|
|
42
|
+
tick() {
|
|
43
|
+
this.#owner.beat();
|
|
44
|
+
const seen = new Set();
|
|
45
|
+
for (const reader of this.#table.claimedSlots()) {
|
|
46
|
+
seen.add(reader.slot);
|
|
47
|
+
const previous = this.#samples.get(reader.slot);
|
|
48
|
+
if (previous === undefined || previous.generation !== reader.generation) {
|
|
49
|
+
// A new claimant, or the same slot reclaimed by someone else. Start fresh.
|
|
50
|
+
this.#samples.set(reader.slot, {
|
|
51
|
+
heartbeat: reader.heartbeat,
|
|
52
|
+
misses: 0,
|
|
53
|
+
generation: reader.generation,
|
|
54
|
+
});
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
// An unpinned reader costs nothing, so it is not worth reaping however idle it is.
|
|
58
|
+
if (reader.epoch === 0 || reader.heartbeat !== previous.heartbeat) {
|
|
59
|
+
previous.heartbeat = reader.heartbeat;
|
|
60
|
+
previous.misses = 0;
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
previous.misses += 1;
|
|
64
|
+
if (previous.misses < this.#missesBeforeDead)
|
|
65
|
+
continue;
|
|
66
|
+
this.#table.forceRelease(reader.slot);
|
|
67
|
+
this.#samples.delete(reader.slot);
|
|
68
|
+
this.#reaped += 1;
|
|
69
|
+
this.#onReaped?.(reader.slot, { epoch: reader.epoch, generation: reader.generation });
|
|
70
|
+
}
|
|
71
|
+
for (const slot of this.#samples.keys()) {
|
|
72
|
+
if (!seen.has(slot))
|
|
73
|
+
this.#samples.delete(slot);
|
|
74
|
+
}
|
|
75
|
+
// Reclaim immediately after reaping, so the memory a dead reader was pinning is returned
|
|
76
|
+
// in the same pass rather than at the next commit.
|
|
77
|
+
this.#owner.reclaim();
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Reclaim a slot straight away, for a window the integration knows is gone.
|
|
81
|
+
*
|
|
82
|
+
* A close or crash event is better evidence than any number of missed heartbeats, so an
|
|
83
|
+
* integration that has one should use it rather than waiting for the detector.
|
|
84
|
+
*/
|
|
85
|
+
reapSlot(slot) {
|
|
86
|
+
this.#table.forceRelease(slot);
|
|
87
|
+
this.#samples.delete(slot);
|
|
88
|
+
this.#reaped += 1;
|
|
89
|
+
this.#owner.reclaim();
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
//# sourceMappingURL=liveness.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"liveness.js","sourceRoot":"","sources":["../../src/liveness.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { SharedArena } from "./arena.js";
|
|
2
|
+
import { Allocator } from "./allocator.js";
|
|
3
|
+
import { ReaderTable } from "./readers.js";
|
|
4
|
+
import { RetainedRing } from "./retained.js";
|
|
5
|
+
import { StringTable } from "./strings.js";
|
|
6
|
+
import { type VerifyModeValue } from "./layout.js";
|
|
7
|
+
export interface OwnerOptions {
|
|
8
|
+
/** Initial arena size in bytes. */
|
|
9
|
+
byteLength?: number;
|
|
10
|
+
/** Upper bound the buffer may grow to. Address space is reserved, not resident memory. */
|
|
11
|
+
maxByteLength?: number;
|
|
12
|
+
/** Reader slots. One per window, plus headroom for reloads that have not been reaped. */
|
|
13
|
+
maxReaders?: number;
|
|
14
|
+
/**
|
|
15
|
+
* How many versions the ring retains. This bounds how far a stalled reader can hold
|
|
16
|
+
* memory: past it, the owner force advances and that reader fails closed.
|
|
17
|
+
*/
|
|
18
|
+
retainedVersions?: number;
|
|
19
|
+
verify?: VerifyModeValue;
|
|
20
|
+
/**
|
|
21
|
+
* How many superseded versions to keep readable for time travel.
|
|
22
|
+
*
|
|
23
|
+
* Zero, the default, reclaims a version as soon as no reader is pinned to it, which is the
|
|
24
|
+
* cheapest thing to do and leaves no history to browse. A positive value keeps that many
|
|
25
|
+
* versions behind the current one alive, at the cost of holding their path copies. It is
|
|
26
|
+
* capped at one less than the retained ring, because the ring is what bounds retention.
|
|
27
|
+
*
|
|
28
|
+
* Turn it on in development and leave it off in production, unless the application has a
|
|
29
|
+
* reason to browse history at runtime.
|
|
30
|
+
*/
|
|
31
|
+
historyDepth?: number;
|
|
32
|
+
}
|
|
33
|
+
export interface OwnerStats {
|
|
34
|
+
readonly versionId: number;
|
|
35
|
+
readonly commits: number;
|
|
36
|
+
readonly forcedAdvances: number;
|
|
37
|
+
readonly liveBytes: number;
|
|
38
|
+
readonly bumpPointer: number;
|
|
39
|
+
readonly capacityBytes: number;
|
|
40
|
+
readonly freeListBytes: number;
|
|
41
|
+
readonly strandedBytes: number;
|
|
42
|
+
readonly internedStrings: number;
|
|
43
|
+
readonly pendingGarbageVersions: number;
|
|
44
|
+
readonly historyDepth: number;
|
|
45
|
+
readonly reclaimFloor: number;
|
|
46
|
+
readonly claimedReaders: number;
|
|
47
|
+
readonly minimumPinnedEpoch: number;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* The sole writer.
|
|
51
|
+
*
|
|
52
|
+
* In Electron this runs in the hidden owner window. In tests it runs on the main thread of
|
|
53
|
+
* a plain Node process, which is exactly the point of keeping this package free of Electron
|
|
54
|
+
* imports.
|
|
55
|
+
*
|
|
56
|
+
* The single writer property is load bearing. It removes allocator locking, it makes the
|
|
57
|
+
* free lists safe to keep on the writer heap where no other window can corrupt them, and it
|
|
58
|
+
* makes the commit sequence a plain store rather than a contended compare and exchange.
|
|
59
|
+
*/
|
|
60
|
+
export declare class ArenaOwner {
|
|
61
|
+
#private;
|
|
62
|
+
readonly arena: SharedArena;
|
|
63
|
+
private constructor();
|
|
64
|
+
/** Allocate and format a new arena. */
|
|
65
|
+
static create(options?: OwnerOptions): ArenaOwner;
|
|
66
|
+
/**
|
|
67
|
+
* Adopt a buffer that a previous owner formatted. Bumps the owner generation so readers
|
|
68
|
+
* attached to the dead owner can detect the change.
|
|
69
|
+
*
|
|
70
|
+
* The free lists cannot be recovered, so an adopted arena starts with a bump pointer
|
|
71
|
+
* where the previous owner left it and no reusable blocks. That is acceptable because
|
|
72
|
+
* adoption follows an owner crash, which is rare and already costs a rehydrate.
|
|
73
|
+
*/
|
|
74
|
+
static adopt(buffer: SharedArrayBuffer): ArenaOwner;
|
|
75
|
+
get buffer(): SharedArrayBuffer;
|
|
76
|
+
get versionId(): number;
|
|
77
|
+
get strings(): StringTable;
|
|
78
|
+
get allocator(): Allocator;
|
|
79
|
+
get readers(): ReaderTable;
|
|
80
|
+
get ring(): RetainedRing;
|
|
81
|
+
/**
|
|
82
|
+
* Encode `value`, publish it as the new root, and reclaim what the previous version was
|
|
83
|
+
* keeping alive.
|
|
84
|
+
*
|
|
85
|
+
* The publication is a seqlock write. Readers never block on it: they observe an odd
|
|
86
|
+
* sequence, spin briefly, and retry. The window is a handful of stores wide.
|
|
87
|
+
*/
|
|
88
|
+
commit(value: unknown): number;
|
|
89
|
+
/**
|
|
90
|
+
* Apply a recipe to a draft of the current state and commit the result.
|
|
91
|
+
*
|
|
92
|
+
* This is the path that makes retention affordable. Only the paths the recipe touched are
|
|
93
|
+
* rebuilt, so setting one field of a large object copies a handful of nodes rather than
|
|
94
|
+
* the tree.
|
|
95
|
+
*
|
|
96
|
+
* owner.update((draft) => {
|
|
97
|
+
* draft.users[3].name = "new name";
|
|
98
|
+
* });
|
|
99
|
+
*/
|
|
100
|
+
update<T>(recipe: (draft: T) => void): number;
|
|
101
|
+
/**
|
|
102
|
+
* The current state as a detached plain value.
|
|
103
|
+
*
|
|
104
|
+
* The owner reads through this rather than through a reader, because it already knows the
|
|
105
|
+
* current root and does not need to pin anything: nothing can reclaim a version while the
|
|
106
|
+
* only writer is inside a synchronous method.
|
|
107
|
+
*/
|
|
108
|
+
readSnapshot(): unknown;
|
|
109
|
+
/**
|
|
110
|
+
* Reclaim every version that no reader can still be pinned to.
|
|
111
|
+
*
|
|
112
|
+
* The order below is the whole correctness argument, so it is written out rather than
|
|
113
|
+
* left to be rediscovered:
|
|
114
|
+
*
|
|
115
|
+
* 1. Scan the reader table for the minimum pinned epoch.
|
|
116
|
+
* 2. Publish the resulting floor.
|
|
117
|
+
* 3. Scan again.
|
|
118
|
+
* 4. Free only below the minimum of the two scans.
|
|
119
|
+
*
|
|
120
|
+
* Step 3 is what closes the race. A reader that pinned between steps 1 and 2 is seen by
|
|
121
|
+
* the second scan. A reader that pinned after step 2 must load the floor after the store
|
|
122
|
+
* that published it, sees its version is below the floor, and retries. There is no
|
|
123
|
+
* interleaving in which a reader proceeds with a version this method then frees.
|
|
124
|
+
*/
|
|
125
|
+
reclaim(): void;
|
|
126
|
+
/** Bump the owner heartbeat. Readers use it to detect an owner that has stopped. */
|
|
127
|
+
beat(): void;
|
|
128
|
+
stats(): OwnerStats;
|
|
129
|
+
}
|
|
130
|
+
//# sourceMappingURL=owner.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"owner.d.ts","sourceRoot":"","sources":["../../src/owner.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AACzC,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAC3C,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAC7C,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAC3C,OAAO,EAAsB,KAAK,eAAe,EAAE,MAAM,aAAa,CAAC;AAavE,MAAM,WAAW,YAAY;IAC3B,mCAAmC;IACnC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,0FAA0F;IAC1F,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,yFAAyF;IACzF,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;OAGG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,MAAM,CAAC,EAAE,eAAe,CAAC;IACzB;;;;;;;;;;OAUG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAWD,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,sBAAsB,EAAE,MAAM,CAAC;IACxC,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,kBAAkB,EAAE,MAAM,CAAC;CACrC;AAED;;;;;;;;;;GAUG;AACH,qBAAa,UAAU;;IACrB,QAAQ,CAAC,KAAK,EAAE,WAAW,CAAC;IAsB5B,OAAO,eAMN;IAED,uCAAuC;IACvC,MAAM,CAAC,MAAM,CAAC,OAAO,GAAE,YAAiB,GAAG,UAAU,CAqBpD;IAED;;;;;;;OAOG;IACH,MAAM,CAAC,KAAK,CAAC,MAAM,EAAE,iBAAiB,GAAG,UAAU,CAYlD;IAED,IAAI,MAAM,IAAI,iBAAiB,CAE9B;IAED,IAAI,SAAS,IAAI,MAAM,CAEtB;IAED,IAAI,OAAO,IAAI,WAAW,CAEzB;IAED,IAAI,SAAS,IAAI,SAAS,CAEzB;IAED,IAAI,OAAO,IAAI,WAAW,CAEzB;IAED,IAAI,IAAI,IAAI,YAAY,CAEvB;IAED;;;;;;OAMG;IACH,MAAM,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAe7B;IAED;;;;;;;;;;OAUG;IACH,MAAM,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,IAAI,GAAG,MAAM,CA4B5C;IAED;;;;;;OAMG;IACH,YAAY,IAAI,OAAO,CAEtB;IAsED;;;;;;;;;;;;;;;OAeG;IACH,OAAO,IAAI,IAAI,CAkBd;IAkED,oFAAoF;IACpF,IAAI,IAAI,IAAI,CAEX;IAED,KAAK,IAAI,UAAU,CAmBlB;CACF"}
|