-
Notifications
You must be signed in to change notification settings - Fork 288
forward worker logs to main thread #2078
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
Changes from all commits
f7496cf
461e901
fbcf500
dfc3ea4
2c68680
eed6580
bbf1598
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| --- | ||
| "livekit-client": patch | ||
| --- | ||
|
|
||
| forward worker logs to main thread |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,7 +3,7 @@ import { EventEmitter } from 'events'; | |
| import type TypedEventEmitter from 'typed-emitter'; | ||
| import type { FrameMetadata } from '../frameMetadata/types'; | ||
| import { hasFrameMetadataPublishOptions } from '../frameMetadata/utils'; | ||
| import log, { LogLevel, workerLogger } from '../logger'; | ||
| import log, { LogLevel, onWorkerLogLevelChanged, workerLogger } from '../logger'; | ||
| import type RTCEngine from '../room/RTCEngine'; | ||
| import type Room from '../room/Room'; | ||
| import { ConnectionState } from '../room/Room'; | ||
|
|
@@ -129,6 +129,9 @@ export class E2EEManager | |
| this.worker.onmessage = this.onWorkerMessage; | ||
| this.worker.onerror = this.onWorkerError; | ||
| this.worker.postMessage(msg); | ||
| onWorkerLogLevelChanged((level) => { | ||
| this.worker?.postMessage({ kind: 'setLogLevel', data: { level } }); | ||
| }); | ||
| } | ||
| } | ||
| } | ||
|
|
@@ -156,25 +159,23 @@ export class E2EEManager | |
| const { kind, data } = ev.data; | ||
| switch (kind) { | ||
| case 'error': | ||
| log.error(data.error.message); | ||
|
|
||
| // If error has uuid, it's from an async operation (encrypt/decrypt) | ||
| // Reject the corresponding future | ||
| // If error has uuid, it's from an async operation (encrypt/decrypt). | ||
| // Reject the corresponding future and let the caller decide how to log/handle; | ||
| // logging here would duplicate whatever the caller does. | ||
| if (data.uuid) { | ||
| const decryptFuture = this.decryptDataRequests.get(data.uuid); | ||
| if (decryptFuture?.reject) { | ||
| decryptFuture.reject(data.error); | ||
| break; // Don't emit general error if it's handled by future | ||
| break; | ||
| } | ||
|
|
||
| const encryptFuture = this.encryptDataRequests.get(data.uuid); | ||
| if (encryptFuture?.reject) { | ||
| encryptFuture.reject(data.error); | ||
| break; // Don't emit general error if it's handled by future | ||
| break; | ||
| } | ||
| } | ||
|
|
||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡 Encryption failures bypass log sinks When Prompt for agentsWas this helpful? React with 👍 or 👎 to provide feedback. |
||
| // Emit general error event for unhandled errors | ||
| this.emit(EncryptionEvent.EncryptionError, data.error, data.participantIdentity); | ||
| break; | ||
| case 'initAck': | ||
|
devin-ai-integration[bot] marked this conversation as resolved.
|
||
|
|
@@ -235,6 +236,9 @@ export class E2EEManager | |
| case 'packetTrailerMetadata': | ||
| this.handleFrameMetadata(data.trackId, data.rtpTimestamp, data.ssrc, data.metadata); | ||
| break; | ||
| case 'log': | ||
| workerLogger[data.level](data.msg, data.context); | ||
| break; | ||
| default: | ||
| break; | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,53 @@ | ||
| import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; | ||
| import { ErrorRateLimiter } from './ErrorRateLimiter'; | ||
|
|
||
| describe('ErrorRateLimiter', () => { | ||
| beforeEach(() => { | ||
| vi.useFakeTimers(); | ||
| vi.setSystemTime(1_000_000); | ||
| }); | ||
|
|
||
| afterEach(() => { | ||
| vi.useRealTimers(); | ||
| }); | ||
|
|
||
| it('emits first call, throttles the immediate next, then allows after throttle window', () => { | ||
| const l = new ErrorRateLimiter(1000, 60_000, 5); | ||
| expect(l.shouldEmit('k')).toBe(true); | ||
| vi.setSystemTime(1_000_500); | ||
| expect(l.shouldEmit('k')).toBe(false); | ||
| vi.setSystemTime(1_001_600); | ||
| expect(l.shouldEmit('k')).toBe(true); | ||
| }); | ||
|
|
||
| it('caps at maxPerWindow and invokes onSuppress once', () => { | ||
| const l = new ErrorRateLimiter(0, 60_000, 3); | ||
| const onSuppress = vi.fn(); | ||
| // one free emit on window reset (count stays 0), then increments to 3 | ||
| expect(l.shouldEmit('k', onSuppress)).toBe(true); | ||
| expect(l.shouldEmit('k', onSuppress)).toBe(true); | ||
| expect(l.shouldEmit('k', onSuppress)).toBe(true); | ||
| expect(l.shouldEmit('k', onSuppress)).toBe(true); | ||
| // now count == 3 == max → suppressed, callback fires once | ||
| expect(l.shouldEmit('k', onSuppress)).toBe(false); | ||
| expect(l.shouldEmit('k', onSuppress)).toBe(false); | ||
| expect(onSuppress).toHaveBeenCalledTimes(1); | ||
| }); | ||
|
|
||
| it('resets count after windowMs', () => { | ||
| const l = new ErrorRateLimiter(0, 1000, 2); | ||
| l.shouldEmit('k'); | ||
| l.shouldEmit('k'); | ||
| l.shouldEmit('k'); | ||
| expect(l.shouldEmit('k')).toBe(false); | ||
| vi.setSystemTime(1_003_000); | ||
| expect(l.shouldEmit('k')).toBe(true); | ||
| }); | ||
|
|
||
| it('tracks keys independently', () => { | ||
| const l = new ErrorRateLimiter(1000); | ||
| expect(l.shouldEmit('a')).toBe(true); | ||
| expect(l.shouldEmit('b')).toBe(true); | ||
| expect(l.shouldEmit('a')).toBe(false); | ||
| }); | ||
| }); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,52 @@ | ||
| /** | ||
| * Per-key rate limiter for repeated errors. Prevents log/emit floods and the | ||
| * unbounded map growth that a per-event log would cause when a broken key | ||
| * keeps producing failures. | ||
| */ | ||
| export class ErrorRateLimiter { | ||
| private lastAt: Map<string, number> = new Map(); | ||
|
|
||
| private counts: Map<string, number> = new Map(); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. question: Do you need to have some way to clear keys in this map before |
||
|
|
||
| constructor( | ||
| private readonly throttleMs: number = 1000, | ||
| private readonly windowMs: number = 60_000, | ||
| private readonly maxPerWindow: number = 5, | ||
| ) {} | ||
|
|
||
| reset() { | ||
| this.lastAt.clear(); | ||
| this.counts.clear(); | ||
| } | ||
|
|
||
| countFor(key: string): number { | ||
| return this.counts.get(key) ?? 0; | ||
| } | ||
|
|
||
| /** | ||
| * Returns true if the caller should emit for this key. Invokes `onSuppress` | ||
| * exactly once per window when the per-window limit is first crossed. | ||
| */ | ||
| shouldEmit(key: string, onSuppress?: () => void): boolean { | ||
| const now = Date.now(); | ||
| const last = this.lastAt.get(key) ?? 0; | ||
| const count = this.counts.get(key) ?? 0; | ||
|
|
||
| if (now - last > this.windowMs) { | ||
| this.counts.set(key, 0); | ||
| this.lastAt.set(key, now); | ||
| return true; | ||
| } | ||
| if (now - last < this.throttleMs) return false; | ||
| if (count >= this.maxPerWindow) { | ||
| if (count === this.maxPerWindow) { | ||
| onSuppress?.(); | ||
| this.counts.set(key, count + 1); | ||
| } | ||
| return false; | ||
| } | ||
| this.lastAt.set(key, now); | ||
| this.counts.set(key, count + 1); | ||
| return true; | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🔴 Discarded encrypted rooms remain retained
Each
setupadds a global log-level listener that permanently captures the manager, worker, and room. Recreating encrypted rooms accumulates retained rooms and redundant worker messages.Prompt for agents
Was this helpful? React with 👍 or 👎 to provide feedback.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
addressed in #2079