Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(daemon): Retain multiple formula numbers per value #2095

Merged
merged 1 commit into from
Feb 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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);
});
Loading