청크 업로드 성능 개선 및 네트워크 속도를 더 정확하게 측정하도록 개선

This commit is contained in:
static
2026-01-12 23:37:04 +09:00
parent a4912c8952
commit 4cdf2b342f
9 changed files with 159 additions and 31 deletions

View File

@@ -0,0 +1,44 @@
export class BoundedQueue<T> {
private isClosed = false;
private reservedCount = 0;
private items: T[] = [];
private waitersNotFull: (() => void)[] = [];
private waitersNotEmpty: (() => void)[] = [];
constructor(private readonly maxSize: number) {}
async push(item: T) {
if (this.isClosed) {
throw new Error("Queue closed");
}
while (this.reservedCount >= this.maxSize) {
await new Promise<void>((resolve) => this.waitersNotFull.push(resolve));
if (this.isClosed) throw new Error("Queue closed");
}
this.reservedCount++;
this.items.push(item);
this.waitersNotEmpty.shift()?.();
}
async pop() {
while (this.items.length === 0) {
if (this.isClosed) return null;
await new Promise<void>((resolve) => this.waitersNotEmpty.push(resolve));
}
const item = this.items.shift()!;
this.reservedCount--;
this.waitersNotFull.shift()?.();
return item;
}
close() {
this.isClosed = true;
while (this.waitersNotEmpty.length > 0) this.waitersNotEmpty.shift()!();
while (this.waitersNotFull.length > 0) this.waitersNotFull.shift()!();
}
}

View File

@@ -0,0 +1,93 @@
type MaybePromise<T> = T | Promise<T> | HybridPromise<T>;
type HybridPromiseState<T> =
| { mode: "sync"; status: "fulfilled"; value: T }
| { mode: "sync"; status: "rejected"; reason: unknown }
| { mode: "async"; promise: Promise<T> };
export class HybridPromise<T> implements PromiseLike<T> {
private isConsumed = false;
private constructor(private readonly state: HybridPromiseState<T>) {
if (state.mode === "sync" && state.status === "rejected") {
queueMicrotask(() => {
if (!this.isConsumed) {
throw state.reason;
}
});
}
}
isSync(): boolean {
return this.state.mode === "sync";
}
toPromise(): Promise<T> {
this.isConsumed = true;
if (this.state.mode === "async") return this.state.promise;
return this.state.status === "fulfilled"
? Promise.resolve(this.state.value)
: Promise.reject(this.state.reason);
}
static resolve<T>(value: MaybePromise<T>): HybridPromise<T> {
if (value instanceof HybridPromise) return value;
return new HybridPromise(
value instanceof Promise
? { mode: "async", promise: value }
: { mode: "sync", status: "fulfilled", value },
);
}
static reject<T = never>(reason?: unknown): HybridPromise<T> {
return new HybridPromise({ mode: "sync", status: "rejected", reason });
}
then<TResult1 = T, TResult2 = never>(
onfulfilled?: ((value: T) => MaybePromise<TResult1>) | null | undefined,
onrejected?: ((reason: unknown) => MaybePromise<TResult2>) | null | undefined,
): HybridPromise<TResult1 | TResult2> {
this.isConsumed = true;
if (this.state.mode === "async") {
return new HybridPromise({
mode: "async",
promise: this.state.promise.then(onfulfilled, onrejected) as any,
});
}
try {
if (this.state.status === "fulfilled") {
if (!onfulfilled) return HybridPromise.resolve(this.state.value as any);
return HybridPromise.resolve(onfulfilled(this.state.value));
} else {
if (!onrejected) return HybridPromise.reject(this.state.reason);
return HybridPromise.resolve(onrejected(this.state.reason));
}
} catch (e) {
return HybridPromise.reject(e);
}
}
catch<TResult = never>(
onrejected?: ((reason: unknown) => MaybePromise<TResult>) | null | undefined,
): HybridPromise<T | TResult> {
return this.then<T, TResult>(null, onrejected);
}
finally(onfinally?: (() => void) | null | undefined): HybridPromise<T> {
this.isConsumed = true;
if (this.state.mode === "async") {
return new HybridPromise({ mode: "async", promise: this.state.promise.finally(onfinally) });
}
try {
onfinally?.();
return new HybridPromise(this.state);
} catch (e) {
return HybridPromise.reject(e);
}
}
}

View File

@@ -0,0 +1,48 @@
export class Scheduler<T = void> {
private isEstimating = false;
private memoryUsage = 0;
private queue: (() => void)[] = [];
constructor(public readonly memoryLimit = 100 * 1024 * 1024 /* 100 MiB */) {}
private next() {
if (!this.isEstimating && this.memoryUsage < this.memoryLimit) {
const resolve = this.queue.shift();
if (resolve) {
this.isEstimating = true;
resolve();
}
}
}
async schedule(
estimateMemoryUsage: number | (() => number | Promise<number>),
task: () => Promise<T>,
) {
if (this.isEstimating || this.memoryUsage >= this.memoryLimit) {
await new Promise<void>((resolve) => {
this.queue.push(resolve);
});
} else {
this.isEstimating = true;
}
let taskMemoryUsage = 0;
try {
taskMemoryUsage =
typeof estimateMemoryUsage === "number" ? estimateMemoryUsage : await estimateMemoryUsage();
this.memoryUsage += taskMemoryUsage;
} finally {
this.isEstimating = false;
this.next();
}
try {
return await task();
} finally {
this.memoryUsage -= taskMemoryUsage;
this.next();
}
}
}

View File

@@ -0,0 +1,3 @@
export * from "./BoundedQueue";
export * from "./HybridPromise";
export * from "./Scheduler";