Skip to content

Commit dc8e020

Browse files
committed
add docs
1 parent 52d312c commit dc8e020

File tree

4 files changed

+97
-0
lines changed

4 files changed

+97
-0
lines changed

api/src/unraid-api/graph/resolvers/docker/container-status.job.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,11 @@ export class ContainerStatusJob implements OnApplicationBootstrap {
1515
private readonly dockerConfigService: DockerConfigService
1616
) {}
1717

18+
/**
19+
* Initialize cron job for refreshing the update status for all containers on a user-configurable schedule.
20+
*
21+
* Does not throw.
22+
*/
1823
onApplicationBootstrap() {
1924
const cronExpression = this.dockerConfigService.getConfig().updateCheckCronSchedule;
2025
const cronJob = CronJob.from({

api/src/unraid-api/graph/resolvers/docker/docker-manifest.service.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,12 @@ export class DockerManifestService {
2828
});
2929
}
3030

31+
/**
32+
* Checks if an update is available for a given container image.
33+
* @param imageRef - The image reference to check, e.g. "unraid/baseimage:latest". If no tag is provided, "latest" is assumed, following the webgui's implementation.
34+
* @param cacheData read from /var/lib/docker/unraid-update-status.json by default
35+
* @returns True if an update is available, false if not, or null if the status is unknown
36+
*/
3137
async isUpdateAvailableCached(imageRef: string, cacheData?: Record<string, CachedStatusEntry>) {
3238
let taggedRef = imageRef;
3339
if (!taggedRef.includes(':')) taggedRef += ':latest';
@@ -38,6 +44,11 @@ export class DockerManifestService {
3844
return containerData.status?.toLowerCase() === 'true';
3945
}
4046

47+
/**
48+
* Checks if a container is rebuild ready.
49+
* @param networkMode - The network mode of the container, e.g. "container:unraid/baseimage:latest".
50+
* @returns True if the container is rebuild ready, false if not
51+
*/
4152
async isRebuildReady(networkMode?: string) {
4253
if (!networkMode || !networkMode.startsWith('container:')) return false;
4354
const target = networkMode.slice('container:'.length);

api/src/unraid-api/graph/resolvers/docker/docker-php.service.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,11 @@ export class DockerPhpService {
113113
}
114114
}
115115

116+
/**
117+
* Gets the update statuses for all containers by triggering `DockerTemplates->getAllInfo(true)` via DockerContainers.php
118+
* @param dockerContainersPath - Path to the DockerContainers.php file
119+
* @returns The update statuses for all containers
120+
*/
116121
async getContainerUpdateStatuses(
117122
dockerContainersPath = '/usr/local/emhttp/plugins/dynamix.docker.manager/include/DockerContainers.php'
118123
): Promise<ExplicitStatusItem[]> {

packages/unraid-shared/src/util/processing.ts

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,15 +34,91 @@ export function makeSafeRunner(onError: (error: unknown) => void) {
3434

3535
type AsyncOperation<T> = () => Promise<T>;
3636

37+
/**
38+
* A mutex for asynchronous operations that ensures only one operation runs at a time.
39+
*
40+
* When multiple callers attempt to execute operations simultaneously, they will all
41+
* receive the same promise from the currently running operation, effectively deduplicating
42+
* concurrent calls. This is useful for expensive operations like API calls, file operations,
43+
* or database queries that should not be executed multiple times concurrently.
44+
*
45+
* @template T - The default return type for operations when using a default operation
46+
*
47+
* @example
48+
* // Basic usage with explicit operations
49+
* const mutex = new AsyncMutex();
50+
*
51+
* // Multiple concurrent calls will deduplicate
52+
* const [result1, result2, result3] = await Promise.all([
53+
* mutex.do(() => fetch('/api/data')),
54+
* mutex.do(() => fetch('/api/data')), // Same request, will get same promise
55+
* mutex.do(() => fetch('/api/data')) // Same request, will get same promise
56+
* ]);
57+
* // Only one fetch actually happens
58+
*
59+
* @example
60+
* // Usage with a default operation
61+
* const dataLoader = new AsyncMutex(() =>
62+
* fetch('/api/expensive-data').then(res => res.json())
63+
* );
64+
*
65+
* // Multiple components can call this without duplication
66+
* const data1 = await dataLoader.do(); // Executes the fetch
67+
* const data2 = await dataLoader.do(); // Gets the same promise result
68+
*/
3769
export class AsyncMutex<T = unknown> {
3870
private currentOperation: Promise<any> | null = null;
3971
private defaultOperation?: AsyncOperation<T>;
4072

73+
/**
74+
* Creates a new AsyncMutex instance.
75+
*
76+
* @param operation - Optional default operation to execute when calling `do()` without arguments.
77+
* This is useful when you have a specific operation that should be deduplicated.
78+
*
79+
* @example
80+
* // Without default operation
81+
* const mutex = new AsyncMutex();
82+
* await mutex.do(() => someAsyncWork());
83+
*
84+
* @example
85+
* // With default operation
86+
* const dataMutex = new AsyncMutex(() => loadExpensiveData());
87+
* await dataMutex.do(); // Executes loadExpensiveData()
88+
*/
4189
constructor(operation?: AsyncOperation<T>) {
4290
this.defaultOperation = operation;
4391
}
4492

93+
/**
94+
* Executes the default operation if one was provided in the constructor.
95+
* @returns Promise that resolves with the result of the default operation
96+
* @throws Error if no default operation was set in the constructor
97+
*/
4598
do(): Promise<T>;
99+
/**
100+
* Executes the provided operation, ensuring only one runs at a time.
101+
*
102+
* If an operation is already running, all subsequent calls will receive
103+
* the same promise from the currently running operation. This effectively
104+
* deduplicates concurrent calls to the same expensive operation.
105+
*
106+
* @param operation - Optional operation to execute. If not provided, uses the default operation.
107+
* @returns Promise that resolves with the result of the operation
108+
* @throws Error if no operation is provided and no default operation was set
109+
*
110+
* @example
111+
* const mutex = new AsyncMutex();
112+
*
113+
* // These will all return the same promise
114+
* const promise1 = mutex.do(() => fetch('/api/data'));
115+
* const promise2 = mutex.do(() => fetch('/api/other')); // Still gets first promise!
116+
* const promise3 = mutex.do(() => fetch('/api/another')); // Still gets first promise!
117+
*
118+
* // After the first operation completes, new operations can run
119+
* await promise1;
120+
* const newPromise = mutex.do(() => fetch('/api/new')); // This will execute
121+
*/
46122
do<U>(operation: AsyncOperation<U>): Promise<U>;
47123
do<U = T>(operation?: AsyncOperation<U>): Promise<U | T> {
48124
if (!operation && !this.defaultOperation) {

0 commit comments

Comments
 (0)