querysub 0.455.0 → 0.456.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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "querysub",
3
- "version": "0.455.0",
3
+ "version": "0.456.0",
4
4
  "main": "index.js",
5
5
  "license": "MIT",
6
6
  "note1": "note on node-forge fork, see https://github.com/digitalbazaar/forge/issues/744 for details",
@@ -585,8 +585,8 @@ export class ArchivesBackblaze {
585
585
  console.error(`[${context}] getaddrinfo ENOTFOUND ${hostname}`, { lookupAddresses, resolveAddresses, apiUrl: api.apiUrl, fullError: err.stack });
586
586
  }
587
587
 
588
- // TODO: Handle if the bucket is deleted?
589
- console.error(`[${context}] giving up after ${3 - retries + 1} attempts: ${err.stack ?? err}`);
588
+ // NOTE: The AI thought case that happens when we run out of retries, that's stupid. This obviously isn't the case. This is the case when it's a normal error, as in the file doesn't exist, we need to throw. We absolutely should not warn here. Warning here wouldn't be anything. It would just be saying, oh, we checked if a file and it didn't, which is normal, which is why we check if a file exists.
589
+
590
590
  throw err;
591
591
  }
592
592
  }
@@ -418,16 +418,27 @@ async function fastMemorySync() {
418
418
  }
419
419
  }
420
420
  let checkNodes = shuffle(Array.from(aliveNodes), Date.now()).slice(0, API_AUDIT_COUNT);
421
- let otherNodesAll = await Promise.all(
422
- checkNodes.map(async nodeId => {
423
- let nodes = await timeoutToUndefinedSilent(200, NodeDiscoveryController.nodes[nodeId].getAllNodeIds());
421
+ let perPeerNodes = await Promise.all(
422
+ checkNodes.map(async peerNodeId => {
423
+ let nodes = await timeoutToUndefinedSilent(200, NodeDiscoveryController.nodes[peerNodeId].getAllNodeIds());
424
424
  if (!nodes) {
425
- deadNodes.set(nodeId, Date.now());
425
+ deadNodes.set(peerNodeId, Date.now());
426
426
  }
427
- return nodes || [];
427
+ return { peerNodeId, nodes: nodes || [] };
428
428
  })
429
429
  );
430
- let otherNodes = Array.from(new Set(otherNodesAll.flat()));
430
+ let reportedBy = new Map<string, string[]>();
431
+ for (let { peerNodeId, nodes } of perPeerNodes) {
432
+ for (let nodeId of nodes) {
433
+ let reporters = reportedBy.get(nodeId);
434
+ if (!reporters) {
435
+ reporters = [];
436
+ reportedBy.set(nodeId, reporters);
437
+ }
438
+ reporters.push(peerNodeId);
439
+ }
440
+ }
441
+ let otherNodes = Array.from(reportedBy.keys());
431
442
  // This would log WAY too much, because we poll a lot, because we want to minimize downtime
432
443
  //console.log(magenta(`Fast memory sync at ${formatVeryNiceDateTime(Date.now())}, nodes found ${otherNodes.length}`), getDebuggerUrl());
433
444
 
@@ -436,8 +447,30 @@ async function fastMemorySync() {
436
447
  // sync now.
437
448
  let missingNodes = otherNodes.filter(nodeId => !allNodeIds2.has(nodeId));
438
449
  if (missingNodes.length > 0) {
439
- console.log(yellow(`Node list is missing nodes, resyncing node`), { missingNodes, otherNodes });
450
+ let missingNodeReporters = Object.fromEntries(
451
+ missingNodes.map(nodeId => [nodeId, reportedBy.get(nodeId) ?? []])
452
+ );
453
+ console.log(yellow(`Node list is missing nodes, resyncing node`), { missingNodes, missingNodeReporters, otherNodes });
440
454
  await syncArchives();
455
+
456
+ // If after syncing our own archive view those nodes are STILL missing, then they are
457
+ // ghosts in the peer's in-memory allNodeIds2 — push a targeted resync to each peer that
458
+ // told us about a ghost, so they can drop it from their own allNodeIds2.
459
+ let stillMissing = missingNodes.filter(nodeId => !allNodeIds2.has(nodeId));
460
+ if (stillMissing.length > 0) {
461
+ let peersToResync = new Set<string>();
462
+ for (let nodeId of stillMissing) {
463
+ for (let peerNodeId of reportedBy.get(nodeId) ?? []) {
464
+ if (isOwnNodeId(peerNodeId)) continue;
465
+ peersToResync.add(peerNodeId);
466
+ }
467
+ }
468
+ let reason = `ghost-resync: peer reported nodes not in our archive after sync: ${stillMissing.join("|")}`;
469
+ console.log(yellow(`Pushing targeted resync to ${peersToResync.size} peers`), { stillMissing, peers: Array.from(peersToResync) });
470
+ for (let peerNodeId of peersToResync) {
471
+ ignoreErrors(NodeDiscoveryController.nodes[peerNodeId].resyncNodes(reason));
472
+ }
473
+ }
441
474
  }
442
475
  }
443
476
 
@@ -5,6 +5,7 @@ import preact from "preact";
5
5
  import { showModal } from "./Modal";
6
6
  import { FullscreenModal } from "./FullscreenModal";
7
7
  import { canHaveChildren } from "socket-function/src/types";
8
+ import { measureBlock } from "socket-function/src/profiling/measure";
8
9
 
9
10
  // Undefined means we infer the column
10
11
  // Null means the column is removed
@@ -24,6 +25,13 @@ export type TableType<RowT extends RowType = RowType> = {
24
25
 
25
26
  module.hotreload = true;
26
27
 
28
+ const SHALLOW_CACHE_MAX_SIZE = 100_000;
29
+ type ShallowCacheEntry = {
30
+ columns: ColumnsType;
31
+ cells: preact.ComponentChild[];
32
+ };
33
+ const shallowCacheMap = new Map<RowType, ShallowCacheEntry>();
34
+
27
35
  export class Table<RowT extends RowType> extends qreact.Component<TableType<RowT> & {
28
36
  class?: string;
29
37
  cellClass?: string;
@@ -34,6 +42,10 @@ export class Table<RowT extends RowType> extends qreact.Component<TableType<RowT
34
42
  characterLimit?: number;
35
43
  dark?: boolean;
36
44
 
45
+ // If true, cache the rendered cells per row (keyed by row reference + columns reference).
46
+ // Resets the cache wholesale once it exceeds SHALLOW_CACHE_MAX_SIZE.
47
+ shallowCache?: boolean;
48
+
37
49
  getRowAttributes?: (row: RowT, index: number) => preact.JSX.HTMLAttributes<HTMLTableRowElement>;
38
50
  }> {
39
51
  state = {
@@ -60,75 +72,93 @@ export class Table<RowT extends RowType> extends qreact.Component<TableType<RowT
60
72
  <th class={css.pad2(8, 4) + cellClass}>{column?.title || toSpaceCase(columnName)}</th>
61
73
  )}
62
74
  </tr>
63
- {rows.map((row, index) => (
64
- <tr
65
- class={
66
- (index % 2 === 1 && css.hsla(0, 0, 100, 0.25) || "")
67
- // NOTE: We don't set z-index, so children z-index values can be laid out above our siblings
68
- // (if we set z-index it will create a new z-index order under us, and our children will never
69
- // be able to above our siblings, which breaks palettes).
70
- + css.relative
75
+ {rows.map((row, index) => {
76
+ const renderCells = () => Object.entries(columns).filter(x => x[1] !== null).map(([columnName, column]) => {
77
+ let value = row[columnName];
78
+ let formatter = column?.formatter || "guess";
79
+ let result = measureBlock(() => formatValue(value, formatter, { row, columnName }), `formatValue|${columnName}`);
80
+ let renderedObj = renderTrimmed({
81
+ content: result,
82
+ lineLimit,
83
+ characterLimit,
84
+ });
85
+ let attributes = { ...renderedObj.outerAttributes };
86
+ attributes.class = attributes.class || "";
87
+ attributes.class += " " + css.whiteSpace("pre-wrap").pad2(8, 4);
88
+ attributes.class += cellClass;
89
+ // If the inner content looks like a VNode, take it's attributes and unwrap it,
90
+ // so it can fill the entire cell.
91
+ let innerContent = renderedObj.innerContent;
92
+ if (
93
+ canHaveChildren(innerContent) && "props" in innerContent
94
+ && canHaveChildren(innerContent.props)
95
+ // Table can't show gap, so don;t collapse if it has gap.
96
+ && !((innerContent.props?.class as string + " " + innerContent.props?.className as string).includes("gap"))
97
+ // NOTE: I'm not sure why we were only elevating the children if we only had 1 child?
98
+ // Changing this will break things, but it should be better overall.
99
+ // && "children" in innerContent.props
100
+ // && (
101
+ // Array.isArray(innerContent.props.children) && innerContent.props.children.length === 1
102
+ // || !Array.isArray(innerContent.props.children)
103
+ // )
104
+ // AND, it is a div (a tags shouldn't be unwrapped)
105
+ && (innerContent.type === "div")
106
+ ) {
107
+ attributes.class += " " + (innerContent.props.class || "");
108
+ attributes.class += " " + (innerContent.props.className || "");
109
+ let baseOnClick = attributes.onClick;
110
+ let props = innerContent.props;
111
+ attributes.onClick = (e) => {
112
+ if (baseOnClick) baseOnClick(e);
113
+ (props as any).onClick?.(e);
114
+ };
115
+ for (let key in props) {
116
+ if (key === "class") continue;
117
+ if (key === "onClick") continue;
118
+ (attributes as any)[key] = props[key];
119
+ }
120
+ innerContent = props.children as any;
71
121
  }
72
- {...this.props.getRowAttributes?.(row, index)}
73
- >
74
- <td class={css.center}>{index + 1}</td>
75
- {Object.entries(columns).filter(x => x[1] !== null).map(([columnName, column]) => {
76
- let value = row[columnName];
77
- let formatter = column?.formatter || "guess";
78
- let result = formatValue(value, formatter, { row, columnName });
79
- let renderedObj = renderTrimmed({
80
- content: result,
81
- lineLimit,
82
- characterLimit,
83
- });
84
- let attributes = { ...renderedObj.outerAttributes };
85
- attributes.class = attributes.class || "";
86
- attributes.class += " " + css.whiteSpace("pre-wrap").pad2(8, 4);
87
- attributes.class += cellClass;
88
- // If the inner content looks like a VNode, take it's attributes and unwrap it,
89
- // so it can fill the entire cell.
90
- let innerContent = renderedObj.innerContent;
91
- if (
92
- canHaveChildren(innerContent) && "props" in innerContent
93
- && canHaveChildren(innerContent.props)
94
- // Table can't show gap, so don;t collapse if it has gap.
95
- && !((innerContent.props?.class as string + " " + innerContent.props?.className as string).includes("gap"))
96
- // NOTE: I'm not sure why we were only elevating the children if we only had 1 child?
97
- // Changing this will break things, but it should be better overall.
98
- // && "children" in innerContent.props
99
- // && (
100
- // Array.isArray(innerContent.props.children) && innerContent.props.children.length === 1
101
- // || !Array.isArray(innerContent.props.children)
102
- // )
103
- // AND, it is a div (a tags shouldn't be unwrapped)
104
- && (innerContent.type === "div")
105
- ) {
106
- attributes.class += " " + (innerContent.props.class || "");
107
- attributes.class += " " + (innerContent.props.className || "");
108
- let baseOnClick = attributes.onClick;
109
- let props = innerContent.props;
110
- attributes.onClick = (e) => {
111
- if (baseOnClick) baseOnClick(e);
112
- (props as any).onClick?.(e);
113
- };
114
- for (let key in props) {
115
- if (key === "class") continue;
116
- if (key === "onClick") continue;
117
- (attributes as any)[key] = props[key];
118
- }
119
- innerContent = props.children as any;
122
+ return <td
123
+ {...attributes}
124
+ data-column={columnName}
125
+ // If it ever becomes non-table cell, it breaks the table
126
+ style={{ display: "table-cell" }}
127
+ >
128
+ {innerContent}
129
+ </td>;
130
+ });
131
+ let cells: preact.ComponentChild[];
132
+ if (this.props.shallowCache) {
133
+ const cached = shallowCacheMap.get(row);
134
+ if (cached && cached.columns === columns) {
135
+ cells = cached.cells;
136
+ } else {
137
+ if (shallowCacheMap.size >= SHALLOW_CACHE_MAX_SIZE) {
138
+ shallowCacheMap.clear();
139
+ }
140
+ cells = renderCells();
141
+ shallowCacheMap.set(row, { columns: columns as ColumnsType, cells });
142
+ }
143
+ } else {
144
+ cells = renderCells();
145
+ }
146
+ return (
147
+ <tr
148
+ class={
149
+ (index % 2 === 1 && css.hsla(0, 0, 100, 0.25) || "")
150
+ // NOTE: We don't set z-index, so children z-index values can be laid out above our siblings
151
+ // (if we set z-index it will create a new z-index order under us, and our children will never
152
+ // be able to above our siblings, which breaks palettes).
153
+ + css.relative
120
154
  }
121
- return <td
122
- {...attributes}
123
- data-column={columnName}
124
- // If it ever becomes non-table cell, it breaks the table
125
- style={{ display: "table-cell" }}
126
- >
127
- {innerContent}
128
- </td>;
129
- })}
130
- </tr>
131
- ))}
155
+ {...this.props.getRowAttributes?.(row, index)}
156
+ >
157
+ <td class={css.center}>{index + 1}</td>
158
+ {cells}
159
+ </tr>
160
+ );
161
+ })}
132
162
  {allRows.length > rows.length && <tr>
133
163
  <td
134
164
  colSpan={1 + Object.keys(columns).length}
@@ -47,7 +47,9 @@ let enableErrorsURL = new URLParam("enableErrors", true);
47
47
  let savedPathsURL = new URLParam("savedPaths", "");
48
48
  let selectedFieldsURL = new URLParam("selectedFields", {} as Record<string, boolean>);
49
49
  let useRelativeTimeURL = new URLParam("useRelativeTime", true);
50
- let showLifecycleColumnURL = new URLParam("showlifecycle", true);
50
+
51
+ // NOTE: Because this doesn't cache properly, it lags a lot, so we shouldn't try to use it.
52
+ let showLifecycleColumnURL = new URLParam("showlifecycle", false);
51
53
 
52
54
  const defaultSelectedFields = {
53
55
  param0: true,
@@ -74,7 +76,7 @@ export class LogViewer3 extends qreact.Component {
74
76
  hasSearched: t.boolean(false),
75
77
  forceMoveStartTime: t.atomic<number | undefined>(undefined),
76
78
  forceMoveEndTime: t.atomic<number | undefined>(undefined),
77
- showLifecycleColumn: t.boolean(true),
79
+ showLifecycleColumn: t.boolean(false),
78
80
  });
79
81
 
80
82
  lifecycleController = LifeCyclesController(SocketFunction.browserNodeId());
@@ -640,6 +642,7 @@ export class LogViewer3 extends qreact.Component {
640
642
  </div>}
641
643
 
642
644
  <Table
645
+ shallowCache
643
646
  rows={this.state.results}
644
647
  columns={columns}
645
648
  lineLimit={4}