Skip to content

Commit

Permalink
refactor(daemon): Retain multiple formula numbers per value (merge #2095
Browse files Browse the repository at this point in the history
)

Replaces the `formulaIdentifierForRef` WeakMap with a WeakMap-backed multimap, enabling the maintenance of one-to-many relationships between values and formula identifiers.
  • Loading branch information
rekmarks authored Feb 22, 2024
2 parents da516dd + 0b723cf commit 08334a3
Show file tree
Hide file tree
Showing 4 changed files with 163 additions and 6 deletions.
12 changes: 8 additions & 4 deletions packages/daemon/src/daemon.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { assertPetName } from './pet-name.js';
import { makeContextMaker } from './context.js';
import { parseFormulaIdentifier } from './formula-identifier.js';
import { makeMutex } from './mutex.js';
import { makeWeakMultimap } from './weak-multimap.js';

const delay = async (ms, cancelled) => {
// Do not attempt to set up a timer if already cancelled.
Expand Down Expand Up @@ -96,12 +97,15 @@ const makeDaemonCore = async (
* @type {Map<string, import('./types.js').Controller>}
*/
const controllerForFormulaIdentifier = new Map();

/**
* Reverse look-up, for answering "what is my name for this near or far
* reference", and not for "what is my name for this promise".
* @type {WeakMap<object, string>}
* @type {import('./types.js').WeakMultimap<Record<string | symbol, unknown>, string>}
*/
const formulaIdentifierForRef = new WeakMap();
const formulaIdentifierForRef = makeWeakMultimap();

/** @type {import('./types.js').WeakMultimap<Record<string | symbol, unknown>, string>['get']} */
const getFormulaIdentifierForRef = ref => formulaIdentifierForRef.get(ref);

/**
Expand Down Expand Up @@ -586,7 +590,7 @@ const makeDaemonCore = async (
context,
external: E.get(partial).external.then(value => {
if (typeof value === 'object' && value !== null) {
formulaIdentifierForRef.set(value, formulaIdentifier);
formulaIdentifierForRef.add(value, formulaIdentifier);
}
return value;
}),
Expand Down Expand Up @@ -669,7 +673,7 @@ const makeDaemonCore = async (
// Release the value to the public only after ensuring
// we can reverse-lookup its nonce.
if (typeof value === 'object' && value !== null) {
formulaIdentifierForRef.set(value, formulaIdentifier);
formulaIdentifierForRef.add(value, formulaIdentifier);
}
return value;
});
Expand Down
40 changes: 38 additions & 2 deletions packages/daemon/src/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -359,7 +359,7 @@ export interface Mail {
remove: PetStore['remove'];
list: PetStore['list'];
identifyLocal: PetStore['identifyLocal'];
reverseLookup: PetStore['reverseLookup'];
reverseLookup(value: unknown): Array<string>;
// Extended methods:
lookup(...petNamePath: string[]): Promise<unknown>;
listSpecial(): Array<string>;
Expand Down Expand Up @@ -637,8 +637,44 @@ export type DaemonicPowers = {
control: DaemonicControlPowers;
};

type Mutex = {
export type Mutex = {
lock: () => Promise<void>;
unlock: () => void;
enqueue: <T>(asyncFn?: () => Promise<T>) => Promise<T>;
};

/**
* A multimap backed by a WeakMap. Keys must be objects.
*/
export type WeakMultimap<K extends WeakKey, V> = {
/**
* @param key - The key to add a value for.
* @param value - The value to add.
*/
add(key: K, value: V): void;

/**
* @param key - The key whose value to delete.
* @param value - The value to delete.
* @returns `true` if the key was found and the value was deleted, `false` otherwise.
*/
delete(key: K, value: V): boolean;

/**
* @param key - The key whose values to delete
* @returns `true` if the key was found and its values were deleted, `false` otherwise.
*/
deleteAll(key: K): boolean;

/**
* @param key - The key whose first value to retrieve
* @returns The first value associated with the key.
*/
get(key: K): V | undefined;

/**
* @param key - The key whose values to retrieve.
* @returns An array of all values associated with the key.
*/
getAll(key: K): V[];
};
35 changes: 35 additions & 0 deletions packages/daemon/src/weak-multimap.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/**
* @returns {import('./types.js').WeakMultimap<WeakKey, any>}
*/
export const makeWeakMultimap = () => {
/** @type {WeakMap<WeakKey, Set<unknown>>} */
const map = new WeakMap();
return {
add: (ref, formulaIdentifier) => {
let set = map.get(ref);
if (set === undefined) {
set = new Set();
map.set(ref, set);
}
set.add(formulaIdentifier);
},

delete: (ref, formulaIdentifier) => {
const set = map.get(ref);
if (set !== undefined) {
const result = set.delete(formulaIdentifier);
if (set.size === 0) {
map.delete(ref);
}
return result;
}
return false;
},

deleteAll: ref => map.delete(ref),

get: ref => map.get(ref)?.keys().next().value,

getAll: ref => Array.from(map.get(ref) ?? []),
};
};
82 changes: 82 additions & 0 deletions packages/daemon/test/test-weak-multimap.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import test from 'ava';
import { makeWeakMultimap } from '../src/weak-multimap.js';

test('add and get', t => {
const multimap = makeWeakMultimap();
const ref = {};
const value = 'foo';

multimap.add(ref, value);
t.is(multimap.get(ref), value);

// Adding a value for a key should be idempotent.
multimap.add(ref, value);
t.is(multimap.get(ref), value);
});

test('add and get with multiple refs', t => {
const multimap = makeWeakMultimap();
const ref1 = {};
const ref2 = {};
const value1 = 'foo';
const value2 = 'bar';

multimap.add(ref1, value1);
multimap.add(ref1, value2);
multimap.add(ref2, value1);

t.is(multimap.get(ref1), value1);
t.deepEqual(multimap.getAll(ref1), [value1, value2]);
t.is(multimap.get(ref2), value1);
t.deepEqual(multimap.getAll(ref2), [value1]);
});

test('getAll', t => {
const multimap = makeWeakMultimap();
const ref = {};
const value1 = 'foo';
const value2 = 'bar';

multimap.add(ref, value1);
multimap.add(ref, value2);
t.deepEqual(multimap.getAll(ref), [value1, value2]);

// Adding a value for a key should be idempotent.
multimap.add(ref, value1);
multimap.add(ref, value2);
t.deepEqual(multimap.getAll(ref), [value1, value2]);
});

test('delete', t => {
const multimap = makeWeakMultimap();
const ref = {};
const value = 'foo';

multimap.add(ref, value);

t.is(multimap.get(ref), value);
t.is(multimap.delete(ref, value), true);
t.is(multimap.get(ref), undefined);

// Deleting should be idempotent.
t.is(multimap.delete(ref, value), false);
t.is(multimap.get(ref), undefined);
});

test('deleteAll', t => {
const multimap = makeWeakMultimap();
const ref = {};
const value1 = 'foo';
const value2 = 'bar';

multimap.add(ref, value1);
multimap.add(ref, value2);

t.deepEqual(multimap.getAll(ref), [value1, value2]);
t.is(multimap.deleteAll(ref), true);
t.is(multimap.get(ref), undefined);

// Deleting should be idempotent.
t.is(multimap.deleteAll(ref), false);
t.is(multimap.get(ref), undefined);
});

0 comments on commit 08334a3

Please sign in to comment.