Skip to content
Merged

Dev #118

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
aeaa225
removeSafe
anthochamp Mar 1, 2026
3f01697
compact
anthochamp Mar 1, 2026
0ef2649
move math functions
anthochamp Mar 1, 2026
f600a54
debounceQueue->serializeQueueNext
anthochamp Mar 1, 2026
c153814
remove ((Maybe)Async)Callback types
anthochamp Mar 1, 2026
e8f32c1
add MaybeAsncDisposable
anthochamp Mar 1, 2026
1dffae8
remove abortable(Async) and AbortablePromise
anthochamp Mar 1, 2026
c0add14
move data structs
anthochamp Mar 1, 2026
3d25ddf
move & fix Counter
anthochamp Mar 1, 2026
cbb6b6c
add Barrier & Latch
anthochamp Mar 1, 2026
8f505b9
locks
anthochamp Mar 1, 2026
2d0be49
add Condition
anthochamp Mar 1, 2026
5283436
add RwLock
anthochamp Mar 1, 2026
71553dc
sync misc
anthochamp Mar 1, 2026
edb021b
add Channel and Broadcast
anthochamp Mar 1, 2026
84b44a2
events misc
anthochamp Mar 1, 2026
db1b361
ISuppressedError use unknown
anthochamp Mar 1, 2026
a95be66
fix printer impl
anthochamp Mar 1, 2026
078f87a
fix vitest extension
anthochamp Mar 1, 2026
dd328f4
index
anthochamp Mar 1, 2026
88fcd82
fix jsonserializeerror
anthochamp Mar 1, 2026
a507d78
AggregateError::errors: unknown
anthochamp Mar 1, 2026
956d636
json serializers
anthochamp Mar 1, 2026
f4123e7
add intersection
anthochamp Mar 1, 2026
396fb6e
add getObjectKeys
anthochamp Mar 1, 2026
59669f9
add traverse
anthochamp Mar 1, 2026
58187f5
upgrade biome & update rules
anthochamp Mar 1, 2026
fe3fb96
remove biome false positive ignore comments
anthochamp Mar 1, 2026
48dde44
fix asctimedate & httptrailers
anthochamp Mar 1, 2026
81a0d5b
enhance dgramsocket
anthochamp Mar 1, 2026
075ea8b
node net
anthochamp Mar 1, 2026
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
2 changes: 1 addition & 1 deletion biome.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"$schema": "https://biomejs.dev/schemas/2.3.13/schema.json",
"$schema": "https://biomejs.dev/schemas/2.4.4/schema.json",
"extends": ["@ac-essentials/biome-config"],
"root": true,
"files": {
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
"devDependencies": {
"@ac-essentials/biome-config": "workspace:*",
"@ac-essentials/markdownlint-cli2-config": "workspace:*",
"@biomejs/biome": "2.3.13",
"@biomejs/biome": "2.4.4",
"@vitest/coverage-v8": "4.0.18",
"is-ci": "4.1.0",
"lefthook": "2.0.16",
Expand Down
1 change: 0 additions & 1 deletion packages/app-util/src/logger/logger-console.ts
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,6 @@ export class LoggerConsole implements Console {
}
}

// biome-ignore lint/nursery/noUnnecessaryConditions: false positive
const selectedProperties = properties
? properties.filter((p) => allProperties.has(p))
: Array.from(allProperties);
Expand Down
81 changes: 46 additions & 35 deletions packages/app-util/src/logger/printers/file-printer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@ import { stat } from "node:fs/promises";
import {
BYTES_PER_MIB,
compressFile,
debounceQueue,
defaults,
existsAsync,
LockHold,
Mutex,
serializeQueueNext,
} from "@ac-essentials/misc-util";
import {
ROTATE_LOG_FILES_DEFAULT_OPTIONS,
Expand Down Expand Up @@ -63,7 +64,7 @@ export class FilePrinter implements ILoggerPrinter {
private textStreamPrinter: TextStreamPrinter | null = null;
private readonly streamPrinterLock = new Mutex();
private readonly handleRotationLock = new Mutex();
private readonly handleRotationDebounced = debounceQueue(() =>
private readonly handleRotationSqn = serializeQueueNext(() =>
this.handleRotation(),
);

Expand All @@ -85,11 +86,14 @@ export class FilePrinter implements ILoggerPrinter {
* and the file stream is properly closed.
*/
async close(): Promise<void> {
await this.streamPrinterLock.withLock(async () => {
{
await using _textStreamPrinterLock = await LockHold.from([
this.streamPrinterLock,
]);
await this._unprotected_close();
});
}

await this.handleRotationDebounced();
await this.handleRotationSqn();
}

/**
Expand All @@ -98,7 +102,7 @@ export class FilePrinter implements ILoggerPrinter {
async flush(): Promise<void> {
await this.textStreamPrinter?.flush();

await this.handleRotationDebounced();
await this.handleRotationSqn();
}

/**
Expand All @@ -107,7 +111,7 @@ export class FilePrinter implements ILoggerPrinter {
async clear(): Promise<void> {
await this.textStreamPrinter?.clear();

await this.handleRotationDebounced();
await this.handleRotationSqn();
}

/**
Expand All @@ -116,13 +120,17 @@ export class FilePrinter implements ILoggerPrinter {
* @param record The log record to print.
*/
async print(record: LoggerRecord): Promise<void> {
await this.streamPrinterLock.withLock(async () => {
{
await using _textStreamPrinterLock = await LockHold.from([
this.streamPrinterLock,
]);

const textStreamPrinter = await this._unprotected_open();

await textStreamPrinter.print(record);
});
}

await this.handleRotationDebounced();
await this.handleRotationSqn();
}

private async _unprotected_open() {
Expand Down Expand Up @@ -183,32 +191,35 @@ export class FilePrinter implements ILoggerPrinter {
}

private async handleRotation() {
return this.handleRotationLock.withLock(async () => {
const { cutOffFileSize, maxFileAgeMs, useCompression } = this.options;

if (cutOffFileSize || maxFileAgeMs) {
let stats: Stats | undefined;
try {
stats = await stat(this.filePath);
} catch {}

if (
stats &&
((cutOffFileSize && stats.size >= cutOffFileSize) ||
(maxFileAgeMs &&
Date.now() - stats.mtime.getTime() >= maxFileAgeMs))
) {
await this.streamPrinterLock.withLock(async () => {
await this._unprotected_close();

await rotateLogFiles(this.filePath, this.options);
});

if (useCompression && (await existsAsync(`${this.filePath}.1`))) {
await compressFile(`${this.filePath}.1`);
}
await using _handleRotationLock = await LockHold.from([
this.handleRotationLock,
]);

const { cutOffFileSize, maxFileAgeMs, useCompression } = this.options;

if (cutOffFileSize || maxFileAgeMs) {
let stats: Stats | undefined;
try {
stats = await stat(this.filePath);
} catch {}

if (
stats &&
((cutOffFileSize && stats.size >= cutOffFileSize) ||
(maxFileAgeMs && Date.now() - stats.mtime.getTime() >= maxFileAgeMs))
) {
{
await using _streamPrinterLock = await LockHold.from([
this.streamPrinterLock,
]);

await rotateLogFiles(this.filePath, this.options);
}

if (useCompression && (await existsAsync(`${this.filePath}.1`))) {
await compressFile(`${this.filePath}.1`);
}
}
});
}
}
}
78 changes: 43 additions & 35 deletions packages/app-util/src/logger/printers/no-repeat-printer-proxy.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import {
debounceQueue,
defaults,
LockHold,
MS_PER_SECOND,
Mutex,
PeriodicalTimer,
Queue,
serializeQueueNext,
} from "@ac-essentials/misc-util";
import type { ILoggerPrinter } from "../logger-printer.js";
import type { LoggerRecord } from "../logger-record.js";
Expand Down Expand Up @@ -37,7 +38,7 @@ export class NoRepeatPrinterProxy implements ILoggerPrinter {
private spool = new Queue<LoggerRecord>();
private readonly spoolLock = new Mutex();
private readonly flushLock = new Mutex();
private readonly flushDebounced = debounceQueue(() => this.flush());
private readonly flushSqn = serializeQueueNext(() => this.flush());
private readonly handleRepeatCountLock = new Mutex();

/**
Expand Down Expand Up @@ -76,41 +77,48 @@ export class NoRepeatPrinterProxy implements ILoggerPrinter {
}

async flush(): Promise<void> {
return this.flushLock.withLock(async () => {
const spool = await this.spoolLock.withLock(() => {
const originalSpool = this.spool;
this.spool = new Queue<LoggerRecord>();
return originalSpool;
});

let record: LoggerRecord | undefined;
while ((record = spool.dequeue()) !== undefined) {
try {
await this.handleRecordPrint(record);
} catch (error) {
// add back the item to the spool if output fails
await this.spoolLock.withLock(() => {
this.spool = new Queue(spool.concat(...this.spool));
});

throw error;
}
await using _flushLock = await LockHold.from([this.flushLock]);

let spool: Queue<LoggerRecord>;
{
await using _spoolLock = await LockHold.from([this.spoolLock]);
spool = this.spool;
this.spool = new Queue<LoggerRecord>();
}

let record: LoggerRecord | undefined;
while ((record = spool.dequeue()) !== undefined) {
try {
await this.handleRecordPrint(record);
} catch (error) {
await using _spoolLock = await LockHold.from([this.spoolLock]);

// add back the item to the spool if output fails
this.spool = new Queue(spool.concat(...this.spool));

throw error;
}
}

return this.printer.flush();
});
return this.printer.flush();
}

async print(record: LoggerRecord): Promise<void> {
await this.spoolLock.withLock(() => {
{
await using _spoolLock = await LockHold.from([this.spoolLock]);
this.spool.enqueue(record);
});
}

await this.flushDebounced();
await this.flushSqn();
}

private async handleRecordPrint(record: LoggerRecord) {
const skipPrint = await this.handleRepeatCountLock.withLock(async () => {
let shouldSkipPrint: boolean;
{
await using _handleRepeatCountLock = await LockHold.from([
this.handleRepeatCountLock,
]);

let recordEqual: boolean = false;
if (this.lastRecord) {
recordEqual = loggerPrinterRecordEqual(record, this.lastRecord, false);
Expand All @@ -122,24 +130,24 @@ export class NoRepeatPrinterProxy implements ILoggerPrinter {

this.lastRecord = record;

const shouldSkipPrint = recordEqual && !counterResetted;
shouldSkipPrint = recordEqual && !counterResetted;

if (shouldSkipPrint) {
this.lastRecordSkipCount++;
}
}

return shouldSkipPrint;
});

if (!skipPrint) {
if (!shouldSkipPrint) {
await this.printer.print(record);
}
}

private async handleTimerTick() {
await this.handleRepeatCountLock.withLock(async () => {
await this._unprotected_handleRepeatCount();
});
await using _handleRepeatCountLock = await LockHold.from([
this.handleRepeatCountLock,
]);

await this._unprotected_handleRepeatCount();
}

private async _unprotected_handleRepeatCount(forceReset = false) {
Expand Down
61 changes: 33 additions & 28 deletions packages/app-util/src/logger/printers/text-stream-printer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@ import type * as fs from "node:fs";
import type * as tty from "node:tty";
import { stripVTControlCharacters } from "node:util";
import {
debounceQueue,
defaults,
LockHold,
Mutex,
Queue,
serializeQueueNext,
} from "@ac-essentials/misc-util";
import type { ILoggerPrinter } from "../logger-printer.js";
import type { LoggerRecord } from "../logger-record.js";
Expand Down Expand Up @@ -36,7 +37,7 @@ export class TextStreamPrinter implements ILoggerPrinter {
private spool = new Queue<TextStreamPrinterSpoolItem>();
private readonly spoolLock = new Mutex();
private readonly flushLock = new Mutex();
private readonly flushDebounced = debounceQueue(() => this.flush());
private readonly flushSqn = serializeQueueNext(() => this.flush());

constructor(
private readonly defaultStream: tty.WriteStream | fs.WriteStream,
Expand All @@ -50,41 +51,44 @@ export class TextStreamPrinter implements ILoggerPrinter {
}

async clear(): Promise<void> {
await this.spoolLock.withLock(() => {
{
await using _flushLock = await LockHold.from([this.flushLock]);

this.spool.clear();

this.spool.enqueue({ isError: false, data: "\x1Bc" });
});
}

await this.flushDebounced();
await this.flushSqn();
}

async close(): Promise<void> {
await this.flush();
}

async flush(): Promise<void> {
return this.flushLock.withLock(async () => {
const spool = await this.spoolLock.withLock(() => {
const originalSpool = this.spool;
this.spool = new Queue<TextStreamPrinterSpoolItem>();
return originalSpool;
});

let item: TextStreamPrinterSpoolItem | undefined;
while ((item = spool.dequeue()) !== undefined) {
try {
await this.outputSpoolItem(item);
} catch (error) {
// add back the items to the spool if output fails
await this.spoolLock.withLock(() => {
this.spool = new Queue(spool.concat(...this.spool));
});

throw error;
}
await using _flushLock = await LockHold.from([this.flushLock]);

let spool: Queue<TextStreamPrinterSpoolItem>;
{
await using _spoolLock = await LockHold.from([this.spoolLock]);
spool = this.spool;
this.spool = new Queue<TextStreamPrinterSpoolItem>();
}

let item: TextStreamPrinterSpoolItem | undefined;
while ((item = spool.dequeue()) !== undefined) {
try {
await this.outputSpoolItem(item);
} catch (error) {
await using _spoolLock = await LockHold.from([this.spoolLock]);

// add back the items to the spool if output fails
this.spool = new Queue(spool.concat(...this.spool));

throw error;
}
});
}
}

async print(record: LoggerRecord): Promise<void> {
Expand All @@ -98,11 +102,12 @@ export class TextStreamPrinter implements ILoggerPrinter {
protected async enqueueSpoolItem(
item: TextStreamPrinterSpoolItem,
): Promise<void> {
await this.spoolLock.withLock(() => {
{
await using _spoolLock = await LockHold.from([this.spoolLock]);
this.spool.enqueue(item);
});
}

await this.flushDebounced();
await this.flushSqn();
}

protected async outputSpoolItem(
Expand Down
Loading