-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add the network object status service (#652)
- Loading branch information
Showing
7 changed files
with
209 additions
and
20 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
import { NetworkObjectDetails } from '@shared/models/network-object.model'; | ||
import logger from '@shared/services/logger.service'; | ||
import { | ||
NetworkObjectStatusRemoteServiceType, | ||
networkObjectStatusServiceNetworkObjectName, | ||
} from '@shared/services/network-object-status.service-model'; | ||
import networkObjectService, { | ||
onDidCreateNetworkObject, | ||
onDidDisposeNetworkObject, | ||
} from '@shared/services/network-object.service'; | ||
|
||
// We are assuming these events get hooked up before any network objects get registered. That allows | ||
// us to start from a clean map. If somehow network objects can be registered before we hook up | ||
// the events, then we have to figure out a way to insert pre-existing objects into the map in a way | ||
// that avoids race conditions with the events that fire around the same time. | ||
const networkObjectIDsToDetails = new Map<string, NetworkObjectDetails>(); | ||
|
||
onDidCreateNetworkObject((networkObjectDetails) => { | ||
if (networkObjectIDsToDetails.has(networkObjectDetails.id)) | ||
logger.warn(`Re-saving network object details for ${networkObjectDetails.id}`); | ||
networkObjectIDsToDetails.set(networkObjectDetails.id, networkObjectDetails); | ||
}); | ||
|
||
onDidDisposeNetworkObject((networkObjectId) => { | ||
if (!networkObjectIDsToDetails.delete(networkObjectId)) | ||
logger.warn(`Notification of disposed object ${networkObjectId} that was previously unknown`); | ||
}); | ||
|
||
// Making this async to align with the service model even though it could really be synchronous | ||
async function getAllNetworkObjectDetails(): Promise<Record<string, NetworkObjectDetails>> { | ||
const allNetworkObjectDetails: Record<string, NetworkObjectDetails> = {}; | ||
networkObjectIDsToDetails.forEach((value: NetworkObjectDetails, key: string) => { | ||
allNetworkObjectDetails[key] = value; | ||
}); | ||
return Promise.resolve(allNetworkObjectDetails); | ||
} | ||
|
||
const networkObjectStatusService: NetworkObjectStatusRemoteServiceType = { | ||
getAllNetworkObjectDetails, | ||
}; | ||
|
||
/** Register the network object that backs the network object status service */ | ||
// This doesn't really represent this service module, so we're not making it default. To use this | ||
// service, you should use `network-object-status.service.ts` | ||
// eslint-disable-next-line import/prefer-default-export | ||
export async function startNetworkObjectStatusService(): Promise<void> { | ||
await networkObjectService.set<NetworkObjectStatusRemoteServiceType>( | ||
networkObjectStatusServiceNetworkObjectName, | ||
networkObjectStatusService, | ||
); | ||
} |
27 changes: 27 additions & 0 deletions
27
src/shared/services/network-object-status.service-model.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
import { NetworkObjectDetails } from '@shared/models/network-object.model'; | ||
|
||
// Functions that are exposed through the network object | ||
export interface NetworkObjectStatusRemoteServiceType { | ||
/** | ||
* Get details about all available network objects | ||
* | ||
* @returns Object whose keys are the names of the network objects and whose values are the | ||
* {@link NetworkObjectDetails} for each network object | ||
*/ | ||
getAllNetworkObjectDetails: () => Promise<Record<string, NetworkObjectDetails>>; | ||
} | ||
|
||
// Functions that are added in the service client on top of what is provided by the network object | ||
/** Provides functions related to the set of available network objects */ | ||
export interface NetworkObjectStatusServiceType extends NetworkObjectStatusRemoteServiceType { | ||
/** | ||
* Get a promise that resolves when a network object is registered or rejects if a timeout is hit | ||
* | ||
* @returns Promise that either resolves to the {@link NetworkObjectDetails} for a network object | ||
* once the network object is registered, or rejects if a timeout is provided and the timeout is | ||
* reached before the network object is registered | ||
*/ | ||
waitForNetworkObject: (id: string, timeoutInMS?: number) => Promise<NetworkObjectDetails>; | ||
} | ||
|
||
export const networkObjectStatusServiceNetworkObjectName = 'NetworkObjectStatusService'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
import { NetworkObjectDetails } from '@shared/models/network-object.model'; | ||
import { | ||
networkObjectStatusServiceNetworkObjectName, | ||
NetworkObjectStatusRemoteServiceType, | ||
NetworkObjectStatusServiceType, | ||
} from '@shared/services/network-object-status.service-model'; | ||
import networkObjectService, { | ||
onDidCreateNetworkObject, | ||
} from '@shared/services/network-object.service'; | ||
import AsyncVariable from '@shared/utils/async-variable'; | ||
|
||
let networkObject: NetworkObjectStatusRemoteServiceType; | ||
let initializationPromise: Promise<void>; | ||
async function initialize(): Promise<void> { | ||
if (!initializationPromise) { | ||
initializationPromise = new Promise<void>((resolve, reject) => { | ||
const executor = async () => { | ||
try { | ||
const localNetworkObjectStatusService = | ||
await networkObjectService.get<NetworkObjectStatusServiceType>( | ||
networkObjectStatusServiceNetworkObjectName, | ||
); | ||
if (!localNetworkObjectStatusService) | ||
throw new Error( | ||
`${networkObjectStatusServiceNetworkObjectName} is not available as a network object`, | ||
); | ||
networkObject = localNetworkObjectStatusService; | ||
resolve(); | ||
} catch (error) { | ||
reject(error); | ||
} | ||
}; | ||
executor(); | ||
}); | ||
} | ||
return initializationPromise; | ||
} | ||
|
||
// If we ever want to be more clever, we could just keep a local (to this process) cache of the | ||
// active network objects. If we do that, we'll have to deal with initial race conditions around | ||
// getting a network object disposed message in this process before handling the snapshot from | ||
// the service host that includes the (now disposed) network object. Just asking the remote service | ||
// is less error prone, but slower, whenever we get a request for the latest network objects. | ||
async function getAllNetworkObjectDetails(): Promise<Record<string, NetworkObjectDetails>> { | ||
await initialize(); | ||
return networkObject.getAllNetworkObjectDetails(); | ||
} | ||
|
||
// Ideally we would use this inside the network object service to be event-based instead of polling | ||
// while waiting for network objects to be created. That would create a circular dependency between | ||
// this service and the network object service, though, which is most easily resolved by merging | ||
// this code into the network object service. That service is pretty big as it is, so to optimize | ||
// for code understandability we'll just leave it as-is and poll inside the network object service | ||
// `get` for now. Other services will have to call this directly if they want to be event based. | ||
async function waitForNetworkObject( | ||
id: string, | ||
timeoutInMS?: number, | ||
): Promise<NetworkObjectDetails> { | ||
const asyncVar = new AsyncVariable<NetworkObjectDetails>(`waiting-for-${id}`, timeoutInMS ?? -1); | ||
// Watch the stream of incoming network objects before getting a snapshot to avoid race conditions | ||
const unsub = onDidCreateNetworkObject((networkObjectDetails) => { | ||
if (networkObjectDetails.id === id) asyncVar.resolveToValue(networkObjectDetails, false); | ||
if (asyncVar.hasSettled) unsub(); | ||
}); | ||
// Now check if the needed network object has already been created | ||
const existingNetworkObjectDetails = await getAllNetworkObjectDetails(); | ||
if (existingNetworkObjectDetails[id]) | ||
asyncVar.resolveToValue(existingNetworkObjectDetails[id], false); | ||
return asyncVar.promise; | ||
} | ||
|
||
const networkObjectStatusService: NetworkObjectStatusServiceType = { | ||
getAllNetworkObjectDetails, | ||
waitForNetworkObject, | ||
}; | ||
|
||
export default networkObjectStatusService; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters