Skip to content

Commit

Permalink
Use thread-sync monotonic timestamp
Browse files Browse the repository at this point in the history
  • Loading branch information
peaBerberian committed Sep 7, 2023
1 parent 607a1fd commit 748fa89
Show file tree
Hide file tree
Showing 40 changed files with 199 additions and 123 deletions.
5 changes: 5 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,11 @@ module.exports = {
"prefer-template": "off",
"no-restricted-properties": [
"error",
{
"object": "performance",
"property": "now",
"message": "Avoid using `performance.now` directly as timestamps may be different in the worker and the main thread. Please use the `getMonotonicTimeStamp` util instead.",
},
{
"object": "window",
"message": "`window` doesn't work in Node.JS and only works when JavaScript is running in the main thread. Please import `globalScope` instead.",
Expand Down
7 changes: 4 additions & 3 deletions src/core/adaptive/buffer_based_chooser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

import log from "../../log";
import arrayFindIndex from "../../utils/array_find_index";
import getMonotonicTimeStamp from "../../utils/monotonic_timestamp";
import getBufferLevels from "./utils/get_buffer_levels";
import {
IRepresentationMaintainabilityScore,
Expand Down Expand Up @@ -94,8 +95,8 @@ export default class BufferBasedChooser {
private _currentEstimate: number | undefined;

/**
* Laast timestamp, in terms of `performance.now`, at which the current
* quality was seen as too high by this algorithm.
* Laast monotonically-raising timestamp, at which the current quality was
* seen as too high by this algorithm.
* Begins at `undefined`.
*/
private _lastUnsuitableQualityTimestamp: number | undefined;
Expand Down Expand Up @@ -166,7 +167,7 @@ export default class BufferBasedChooser {
bufferGap :
0;

const now = performance.now();
const now = getMonotonicTimeStamp();

if (
actualBufferGap < bufferLevels[currentBitrateIndex] ||
Expand Down
7 changes: 4 additions & 3 deletions src/core/adaptive/guess_based_chooser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import log from "../../log";
import { Representation } from "../../manifest";
import arrayFindIndex from "../../utils/array_find_index";
import getMonotonicTimeStamp from "../../utils/monotonic_timestamp";
import { estimateRequestBandwidth } from "./network_analyzer";
import LastEstimateStorage, {
ABRAlgorithmType,
Expand Down Expand Up @@ -146,7 +147,7 @@ export default class GuessBasedChooser {
if (shouldStopGuess) {
// Block guesses for a time
this._consecutiveWrongGuesses++;
this._blockGuessesUntil = performance.now() +
this._blockGuessesUntil = getMonotonicTimeStamp() +
Math.min(this._consecutiveWrongGuesses * 15000, 120000);
return getPreviousRepresentation(representations, currentRepresentation);
} else if (scoreData === undefined) {
Expand Down Expand Up @@ -177,7 +178,7 @@ export default class GuessBasedChooser {
{ score, confidenceLevel } : IRepresentationMaintainabilityScore
) : boolean {
return isFinite(bufferGap) && bufferGap >= 2.5 &&
performance.now() > this._blockGuessesUntil &&
getMonotonicTimeStamp() > this._blockGuessesUntil &&
confidenceLevel === ScoreConfidenceLevel.HIGH &&
score / speed > 1.01;
}
Expand Down Expand Up @@ -207,7 +208,7 @@ export default class GuessBasedChooser {
return req.content.representation.id === lastGuess.id;
});

const now = performance.now();
const now = getMonotonicTimeStamp();
for (const req of guessedRepresentationRequests) {
const requestElapsedTime = now - req.requestTimestamp;
if (req.content.segment.isInit) {
Expand Down
5 changes: 3 additions & 2 deletions src/core/adaptive/network_analyzer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import config from "../../config";
import log from "../../log";
import { Representation } from "../../manifest";
import arrayFind from "../../utils/array_find";
import getMonotonicTimeStamp from "../../utils/monotonic_timestamp";
import {
IRepresentationEstimatorPlaybackObservation,
} from "./adaptive_representation_selector";
Expand Down Expand Up @@ -160,7 +161,7 @@ function estimateStarvationModeBitrate(
}

const concernedRequest = concernedRequests[0];
const now = performance.now();
const now = getMonotonicTimeStamp();

let minimumRequestTime = concernedRequest.content.segment.duration * 1.5;
minimumRequestTime = Math.min(minimumRequestTime, 3000);
Expand Down Expand Up @@ -243,7 +244,7 @@ function shouldDirectlySwitchToLowBitrate(
return true;
}

const now = performance.now();
const now = getMonotonicTimeStamp();
const lastProgressEvent = nextRequest.progress.length > 0 ?
nextRequest.progress[nextRequest.progress.length - 1] :
undefined;
Expand Down
12 changes: 9 additions & 3 deletions src/core/adaptive/utils/pending_requests_store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,10 @@ export interface IPendingRequestStoreProgress {
id: string;
/** Current downloaded size, in bytes. */
size : number;
/** Value of `performance.now` at the time this progression report was available. */
/**
* Value of the monotonically-raising timestamp at the time this progression
* report was available.
*/
timestamp : number;
/**
* Total size of the segment to download (including already-loaded data),
Expand All @@ -121,7 +124,7 @@ export interface IPendingRequestStoreBegin {
* should have this `id` at the same time.
*/
id: string;
/** Value of `performance.now` at the time the request began. */
/** Value of the monotonically-raising timestamp at the time the request began. */
requestTimestamp: number;
/** Context associated to the segment. */
content: IRequestInfoContent;
Expand All @@ -131,7 +134,10 @@ export interface IPendingRequestStoreBegin {
export interface IRequestInfo {
/** Information on the current progress made by this request. */
progress: IPendingRequestStoreProgress[];
/** `Performance.now()` corresponding to the time at which the request began. */
/**
* Monotonically-raising timestamp corresponding to the time at which the
* request began.
*/
requestTimestamp: number;
/** Context associated to the segment. */
content: IRequestInfoContent;
Expand Down
4 changes: 3 additions & 1 deletion src/core/api/debug/buffer_size_graph.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import getMonotonicTimeStamp from "../../../utils/monotonic_timestamp";

/**
* Maximum history of the buffer size that will be displayed, in milliseconds.
* For example, a value of `3000` indicates that we will just show at most the
Expand Down Expand Up @@ -30,7 +32,7 @@ export default class BufferSizeGraph {
}

public pushBufferSize(bufferSize : number) : void {
const now = performance.now();
const now = getMonotonicTimeStamp();
this._history.push({ timestamp: now, bufferSize });
if (this._history.length > 0) {
const minimumTime = now - TIME_SAMPLES_MS;
Expand Down
15 changes: 11 additions & 4 deletions src/core/api/playback_observer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

import config from "../../config";
import log from "../../log";
import getMonotonicTimeStamp from "../../utils/monotonic_timestamp";
import noop from "../../utils/noop";
import objectAssign from "../../utils/object_assign";
import { getRange } from "../../utils/ranges";
Expand Down Expand Up @@ -455,7 +456,10 @@ export interface IRebufferingStatus {
reason : "seeking" | // Building buffer after seeking
"not-ready" | // Building buffer after low readyState
"buffering"; // Other cases
/** `performance.now` at the time the rebuffering happened. */
/**
* Monotonically-raising timestamp at the time the rebuffering happened on the
* main thread.
*/
timestamp : number;
/**
* Position, in seconds, at which data is awaited.
Expand All @@ -471,7 +475,10 @@ export interface IRebufferingStatus {
* an unknown reason.
*/
export interface IFreezingStatus {
/** `performance.now` at the time the freezing started to be detected. */
/**
* Monotonically-raising timestamp at the time the freezing started to be
* detected.
*/
timestamp : number;
}

Expand Down Expand Up @@ -834,7 +841,7 @@ function getRebufferingStatus(
position: rebufferEndPosition };
}
return { reason,
timestamp: performance.now(),
timestamp: getMonotonicTimeStamp(),
position: rebufferEndPosition };
}
return null;
Expand Down Expand Up @@ -875,7 +882,7 @@ function getFreezingStatus(
currentInfo.readyState >= 1 &&
currentInfo.playbackRate !== 0 &&
currentInfo.position === prevObservation.position ?
{ timestamp: performance.now() } :
{ timestamp: getMonotonicTimeStamp() } :
null;
}

Expand Down
3 changes: 3 additions & 0 deletions src/core/api/public_api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ import EventEmitter, {
import idGenerator from "../../utils/id_generator";
import isNullOrUndefined from "../../utils/is_null_or_undefined";
import Logger from "../../utils/logger";
import getMonotonicTimeStamp from "../../utils/monotonic_timestamp";
import objectAssign from "../../utils/object_assign";
import { getLeftSizeOfRange } from "../../utils/ranges";
import createSharedReference, {
Expand Down Expand Up @@ -402,6 +403,8 @@ class Player extends EventEmitter<IPublicAPIEvent> {
value: {
wasmUrl: workerOptions.dashWasmUrl,
logLevel: log.getLevel(),
date: Date.now(),
timestamp: getMonotonicTimeStamp(),
},
});
log.addEventListener("onLogLevelChange", (logLevel) => {
Expand Down
25 changes: 13 additions & 12 deletions src/core/fetchers/manifest/manifest_fetcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import {
} from "../../../transports";
import EventEmitter from "../../../utils/event_emitter";
import isNullOrUndefined from "../../../utils/is_null_or_undefined";
import getMonotonicTimeStamp from "../../../utils/monotonic_timestamp";
import noop from "../../../utils/noop";
import TaskCanceller from "../../../utils/task_canceller";
import errorSelector from "../utils/error_selector";
Expand Down Expand Up @@ -282,7 +283,7 @@ export default class ManifestFetcher extends EventEmitter<IManifestFetcherEvent>
parserOptions : IManifestFetcherParserOptions,
requestUrl : string | undefined
) : Promise<IManifestFetcherParsedResult> {
const parsingTimeStart = performance.now();
const parsingTimeStart = getMonotonicTimeStamp();
const cancelSignal = this._canceller.signal;
const trigger = this.trigger.bind(this);
const { sendingTime, receivedTime } = loaded;
Expand Down Expand Up @@ -361,7 +362,7 @@ export default class ManifestFetcher extends EventEmitter<IManifestFetcherEvent>
warnings: IPlayerError[]
) : IManifestFetcherParsedResult {
onWarnings(warnings);
const parsingTime = performance.now() - parsingTimeStart;
const parsingTime = getMonotonicTimeStamp() - parsingTimeStart;
log.info(`MF: Manifest parsed in ${parsingTime}ms`);

return { manifest,
Expand Down Expand Up @@ -435,7 +436,7 @@ export default class ManifestFetcher extends EventEmitter<IManifestFetcherEvent>
/** Time elapsed since the beginning of the Manifest request, in milliseconds. */
const timeSinceRequest = sendingTime === undefined ?
0 :
performance.now() - sendingTime;
getMonotonicTimeStamp() - sendingTime;

/** Minimum update delay we should not go below, in milliseconds. */
const minInterval = Math.max(this._settings.minimumManifestUpdateInterval -
Expand All @@ -458,7 +459,7 @@ export default class ManifestFetcher extends EventEmitter<IManifestFetcherEvent>
// (to avoid asking for it too often).
const timeSinceLastRefresh = sendingTime === undefined ?
0 :
performance.now() - sendingTime;
getMonotonicTimeStamp() - sendingTime;
const _minInterval = Math.max(this._settings.minimumManifestUpdateInterval -
timeSinceLastRefresh,
0);
Expand Down Expand Up @@ -598,7 +599,7 @@ export default class ManifestFetcher extends EventEmitter<IManifestFetcherEvent>
const { manifest: newManifest,
sendingTime: newSendingTime,
parsingTime } = res;
const updateTimeStart = performance.now();
const updateTimeStart = getMonotonicTimeStamp();

if (fullRefresh) {
manifest.replace(newManifest);
Expand All @@ -615,8 +616,8 @@ export default class ManifestFetcher extends EventEmitter<IManifestFetcherEvent>
// The value allows to set a delay relatively to the last Manifest refresh
// (to avoid asking for it too often).
const timeSinceLastRefresh = newSendingTime === undefined ?
0 :
performance.now() - newSendingTime;
0 :
getMonotonicTimeStamp() - newSendingTime;
const _minInterval = Math.max(this._settings.minimumManifestUpdateInterval -
timeSinceLastRefresh,
0);
Expand All @@ -635,7 +636,7 @@ export default class ManifestFetcher extends EventEmitter<IManifestFetcherEvent>
return;
}
}
const updatingTime = performance.now() - updateTimeStart;
const updatingTime = getMonotonicTimeStamp() - updateTimeStart;
this._recursivelyRefreshManifest(manifest, { sendingTime: newSendingTime,
parsingTime,
updatingTime });
Expand Down Expand Up @@ -670,11 +671,11 @@ interface IManifestFetcherParsedResult {
/** The resulting Manifest */
manifest : Manifest;
/**
* The time (`performance.now()`) at which the request was started (at which
* Monotonically-raising timestamp at which the request was started (at which
* the JavaScript call was done).
*/
sendingTime? : number | undefined;
/** The time (`performance.now()`) at which the request was fully received. */
/** Monotonically-raising timestamp at which the request was fully received. */
receivedTime? : number | undefined;
/* The time taken to parse the Manifest through the corresponding parse function. */
parsingTime? : number | undefined;
Expand All @@ -689,8 +690,8 @@ interface IManifestFetcherResponse {

interface IManifestFetcherParserOptions {
/**
* If set, offset to add to `performance.now()` to obtain the current
* server's time.
* If set, offset to add to the monotonically-raising timestamp to obtain the
* current server's time.
*/
externalClockOffset? : number | undefined;
/** The previous value of the Manifest (when updating). */
Expand Down
5 changes: 3 additions & 2 deletions src/core/fetchers/segment/segment_fetcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import arrayIncludes from "../../../utils/array_includes";
import idGenerator from "../../../utils/id_generator";
import InitializationSegmentCache from "../../../utils/initialization_segment_cache";
import isNullOrUndefined from "../../../utils/is_null_or_undefined";
import getTimestamp from "../../../utils/monotonic_timestamp";
import objectAssign from "../../../utils/object_assign";
import {
CancellationError,
Expand Down Expand Up @@ -179,7 +180,7 @@ export default function createSegmentFetcher<TLoadedFormat, TSegmentDataType>(
lifecycleCallbacks.onProgress?.({ duration: info.duration,
size: info.size,
totalSize: info.totalSize,
timestamp: performance.now(),
timestamp: getTimestamp(),
id: requestId });
}
},
Expand All @@ -205,7 +206,7 @@ export default function createSegmentFetcher<TLoadedFormat, TSegmentDataType>(
}

log.debug("SF: Beginning request", segmentIdString);
lifecycleCallbacks.onRequestBegin?.({ requestTimestamp: performance.now(),
lifecycleCallbacks.onRequestBegin?.({ requestTimestamp: getTimestamp(),
id: requestId,
content });

Expand Down
11 changes: 6 additions & 5 deletions src/core/fetchers/utils/schedule_request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import log from "../../../log";
import { ICdnMetadata } from "../../../parsers/manifest";
import cancellableSleep from "../../../utils/cancellable_sleep";
import getFuzzedDelay from "../../../utils/get_fuzzed_delay";
import getTimestamp from "../../../utils/monotonic_timestamp";
import noop from "../../../utils/noop";
import TaskCanceller, {
CancellationSignal,
Expand Down Expand Up @@ -248,7 +249,7 @@ export async function scheduleRequestWithCdns<T>(
const delay = Math.min(baseDelay * Math.pow(2, errorCounter - 1),
maxDelay);
const fuzzedDelay = getFuzzedDelay(delay);
missedAttemptsObj.blockedUntil = performance.now() + fuzzedDelay;
missedAttemptsObj.blockedUntil = getTimestamp() + fuzzedDelay;
}

return retryWithNextCdn(error);
Expand Down Expand Up @@ -303,7 +304,7 @@ export async function scheduleRequestWithCdns<T>(
return requestCdn(nextWantedCdn);
}

const now = performance.now();
const now = getTimestamp();
const blockedFor = nextCdnAttemptObj.blockedUntil - now;
if (blockedFor <= 0) {
return requestCdn(nextWantedCdn);
Expand Down Expand Up @@ -360,7 +361,7 @@ export async function scheduleRequestWithCdns<T>(
if (missedAttempts.size === 0) {
return sortedCdns[0];
}
const now = performance.now();
const now = getTimestamp();
return sortedCdns
.filter(c => missedAttempts.get(c)?.isBlacklisted !== true)
.reduce((
Expand Down Expand Up @@ -427,8 +428,8 @@ interface ICdnAttemptMetadata {
*/
errorCounter : number;
/**
* Timestamp, in terms of `performance.now()`, until which it should be
* forbidden to request this CDN.
* Monotonically-raising timestamp, until which it should be forbidden to
* request this CDN.
* Enforcing this delay allows to prevent making too much requests to a given
* CDN.
*
Expand Down
Loading

0 comments on commit 748fa89

Please sign in to comment.