querysub 0.487.0 → 0.489.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/package.json
CHANGED
|
@@ -21,6 +21,7 @@ import { Time } from "./pathValueCore";
|
|
|
21
21
|
import { encodeCborx, decodeCborx } from "../misc/cloneHelpers";
|
|
22
22
|
import { debugGetAllCallFactories } from "socket-function/src/nodeCache";
|
|
23
23
|
import { delay } from "socket-function/src/batching";
|
|
24
|
+
import { isDefined } from "../misc";
|
|
24
25
|
export { pathValueCommitter };
|
|
25
26
|
|
|
26
27
|
let pathValueSendCount = 0;
|
|
@@ -103,12 +104,14 @@ export class PathValueControllerBase {
|
|
|
103
104
|
pathValues: PathValue[];
|
|
104
105
|
initialTriggers?: { values: Set<string>; parentPaths: Set<string> },
|
|
105
106
|
reason: string;
|
|
107
|
+
// Locks are normally stripped because watchers don't need them, but a receiving authority needs the locks to compute the valid state itself — without them it can only adopt our valid state and can never recompute.
|
|
108
|
+
keepLocks?: boolean;
|
|
106
109
|
}) {
|
|
107
110
|
let changes = config.pathValues;
|
|
108
111
|
let { nodeId, initialTriggers } = config;
|
|
109
112
|
pathValueSendCount += changes.length;
|
|
110
113
|
let buffers = await pathValueSerializer.serialize(changes, {
|
|
111
|
-
noLocks:
|
|
114
|
+
noLocks: !config.keepLocks,
|
|
112
115
|
compress: getCompressNetwork(),
|
|
113
116
|
});
|
|
114
117
|
this.logSendValues({ nodeId, pathValues: changes, initialTriggers, reason: config.reason });
|
|
@@ -182,6 +185,9 @@ export class PathValueControllerBase {
|
|
|
182
185
|
}
|
|
183
186
|
}
|
|
184
187
|
|
|
188
|
+
// Note which values are genuinely new to us BEFORE ingesting. We only re-share new values with the other authorities below; if we already had a value we already shared it when we first received it, so re-sharing is redundant and can clobber a newer value with an older copy.
|
|
189
|
+
let valuesNewToUs = config.initialCreation && values.filter(value => !authorityStorage.getValueExactMaybeRejected(value.path, value.time)) || [];
|
|
190
|
+
|
|
185
191
|
try {
|
|
186
192
|
let initialTriggers = config.initialTriggers || { values: new Set(), parentPaths: new Set() };
|
|
187
193
|
await pathValueCommitter.ingestRemoteValuesAndValidStates({
|
|
@@ -194,9 +200,10 @@ export class PathValueControllerBase {
|
|
|
194
200
|
}
|
|
195
201
|
|
|
196
202
|
|
|
197
|
-
if (config.initialCreation) {
|
|
198
|
-
//
|
|
199
|
-
|
|
203
|
+
if (config.initialCreation && valuesNewToUs.length > 0) {
|
|
204
|
+
// Always shared the latest. If we don't have it, maybe something with ingestion delayed, we should still share it so we don't lose the value. If we haven't even ingested it, then it is our latest version of the value.
|
|
205
|
+
valuesNewToUs = valuesNewToUs.map(value => authorityStorage.getValueExactMaybeRejected(value.path, value.time) || value);
|
|
206
|
+
await PathValueControllerBase.authorityShareValues({ pathValues: valuesNewToUs });
|
|
200
207
|
}
|
|
201
208
|
}
|
|
202
209
|
|
|
@@ -211,6 +218,7 @@ export class PathValueControllerBase {
|
|
|
211
218
|
nodeId: otherAuthority,
|
|
212
219
|
pathValues: values,
|
|
213
220
|
reason: "authorityShareValues",
|
|
221
|
+
keepLocks: true,
|
|
214
222
|
}), () => new Error(`Timeout forwarding shared values (${values.length} values) to authority ${otherAuthority}`));
|
|
215
223
|
} catch (error: any) {
|
|
216
224
|
console.error(error.message, { otherAuthority, count: values.length });
|
|
@@ -92,11 +92,7 @@ class ValidStateComputer {
|
|
|
92
92
|
return true;
|
|
93
93
|
});
|
|
94
94
|
|
|
95
|
-
// For any value we're the authority on that we already have, swap in our own stored copy. The data
|
|
96
|
-
// is immutable per (path, time) — only the valid state can differ, and we own that — so we must never
|
|
97
|
-
// adopt a sibling authority's valid state. Remapping (rather than only skipping the re-ingest) keeps
|
|
98
|
-
// everything downstream (valuesChanged, dependenciesChanged) on a single object instead of a mix of
|
|
99
|
-
// our copy and the remote's.
|
|
95
|
+
// For any value we're the authority on that we already have, swap in our own stored copy. The data is immutable per (path, time) — only the valid state can differ, and we own that — so we must never adopt a sibling authority's valid state. Remapping (rather than only skipping the re-ingest) keeps everything downstream (valuesChanged, dependenciesChanged) on a single object instead of a mix of our copy and the remote's.
|
|
100
96
|
pathValues = pathValues.map(value => {
|
|
101
97
|
if (!PathRouter.isSelfAuthority(value.path)) return value;
|
|
102
98
|
return authorityStorage.getValueExactMaybeRejected(value.path, value.time) || value;
|
|
@@ -125,7 +121,9 @@ class ValidStateComputer {
|
|
|
125
121
|
dependenciesChanged.add(watcher);
|
|
126
122
|
}
|
|
127
123
|
|
|
128
|
-
|
|
124
|
+
// We don't compute the valid state for values we don't own, but we still forward them to our watchers, and that must include accept transitions, not only rejects — otherwise a watcher we already forwarded a reject to stays stuck once the authority accepts. Ingest happens further below, so getValueExactMaybeRejected still returns our previous value here; only trigger when the incoming valid differs from what we currently hold, so we forward real changes without re-triggering on unchanged re-sends.
|
|
125
|
+
let prevValidState = authorityStorage.getValueExactMaybeRejected(value.path, value.time)?.valid;
|
|
126
|
+
if (prevValidState !== value.valid) {
|
|
129
127
|
initialTriggers.initialTriggerNonHistoryWatchers.add(value.path);
|
|
130
128
|
}
|
|
131
129
|
}
|
|
@@ -232,10 +230,8 @@ class ValidStateComputer {
|
|
|
232
230
|
for (let validStateChange of validStateChanged) {
|
|
233
231
|
valuesChanged.add(validStateChange);
|
|
234
232
|
|
|
235
|
-
//
|
|
236
|
-
|
|
237
|
-
initialTriggers.initialTriggerNonHistoryWatchers.add(validStateChange.path);
|
|
238
|
-
}
|
|
233
|
+
// Push the latest value to this path's (possibly cross-node) watchers on ANY valid-state change, not just rejections — external watchers are only re-notified via initialTriggerNonHistoryWatchers; valuesChanged alone propagates between co-authorities and never re-notifies a watcher that already holds a stale verdict, so without notifying on accepts a reject→accept recovery is computed correctly on the authority but the watcher stays stuck on the earlier rejected value.
|
|
234
|
+
initialTriggers.initialTriggerNonHistoryWatchers.add(validStateChange.path);
|
|
239
235
|
|
|
240
236
|
// // NOTE: As of right now, I think the only reason to force an initial trigger in this case is so that we can bust all the values through the query sub http server to the clients. Because if we just tell the HTTP server that one value has been rejected, it can go tell its clients, but that won't give them the latest value. Which isn't great. We should really have something where we can tell them. HMM...
|
|
241
237
|
// // IMPORTANT! The consequence of this is that if someone predicts a value, sends it to us, it's not valid, we will see it's not valid, and we will tell all of the nodes that are watching that path about all of the values on that path. This is inefficient. However, rejected values should be quite rare, so it shouldn't be too inefficient.
|