You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
170 lines
5.8 KiB
JavaScript
170 lines
5.8 KiB
JavaScript
2 years ago
|
/*
|
||
|
* Copyright DataStax, Inc.
|
||
|
*
|
||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||
|
* you may not use this file except in compliance with the License.
|
||
|
* You may obtain a copy of the License at
|
||
|
*
|
||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||
|
*
|
||
|
* Unless required by applicable law or agreed to in writing, software
|
||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
|
* See the License for the specific language governing permissions and
|
||
|
* limitations under the License.
|
||
|
*/
|
||
|
'use strict';
|
||
|
|
||
|
const util = require('util');
|
||
|
const { Long } = require('../types');
|
||
|
const errors = require('../errors');
|
||
|
|
||
|
/** @module policies/timestampGeneration */
|
||
|
|
||
|
/**
|
||
|
* Defines the maximum date in milliseconds that can be represented in microseconds using Number ((2 ^ 53) / 1000)
|
||
|
* @const
|
||
|
* @private
|
||
|
*/
|
||
|
const _maxSafeNumberDate = 9007199254740;
|
||
|
|
||
|
/**
|
||
|
* A long representing the value 1000
|
||
|
* @const
|
||
|
* @private
|
||
|
*/
|
||
|
const _longOneThousand = Long.fromInt(1000);
|
||
|
|
||
|
/**
|
||
|
* Creates a new instance of {@link TimestampGenerator}.
|
||
|
* @classdesc
|
||
|
* Generates client-side, microsecond-precision query timestamps.
|
||
|
* <p>
|
||
|
* Given that Cassandra uses those timestamps to resolve conflicts, implementations should generate
|
||
|
* monotonically increasing timestamps for successive invocations of {@link TimestampGenerator.next()}.
|
||
|
* </p>
|
||
|
* @constructor
|
||
|
*/
|
||
|
function TimestampGenerator() {
|
||
|
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the next timestamp.
|
||
|
* <p>
|
||
|
* Implementors should enforce increasing monotonicity of timestamps, that is,
|
||
|
* a timestamp returned should always be strictly greater that any previously returned
|
||
|
* timestamp.
|
||
|
* <p/>
|
||
|
* <p>
|
||
|
* Implementors should strive to achieve microsecond precision in the best possible way,
|
||
|
* which is usually largely dependent on the underlying operating system's capabilities.
|
||
|
* </p>
|
||
|
* @param {Client} client The {@link Client} instance to generate timestamps to.
|
||
|
* @returns {Long|Number|null} the next timestamp (in microseconds). If it's equals to <code>null</code>, it won't be
|
||
|
* sent by the driver, letting the server to generate the timestamp.
|
||
|
* @abstract
|
||
|
*/
|
||
|
TimestampGenerator.prototype.next = function (client) {
|
||
|
throw new Error('next() must be implemented');
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* A timestamp generator that guarantees monotonically increasing timestamps and logs warnings when timestamps
|
||
|
* drift in the future.
|
||
|
* <p>
|
||
|
* {@link Date} has millisecond precision and client timestamps require microsecond precision. This generator
|
||
|
* keeps track of the last generated timestamp, and if the current time is within the same millisecond as the last,
|
||
|
* it fills the microsecond portion of the new timestamp with the value of an incrementing counter.
|
||
|
* </p>
|
||
|
* @param {Number} [warningThreshold] Determines how far in the future timestamps are allowed to drift before a
|
||
|
* warning is logged, expressed in milliseconds. Default: <code>1000</code>.
|
||
|
* @param {Number} [minLogInterval] In case of multiple log events, it determines the time separation between log
|
||
|
* events, expressed in milliseconds. Use 0 to disable. Default: <code>1000</code>.
|
||
|
* @extends {TimestampGenerator}
|
||
|
* @constructor
|
||
|
*/
|
||
|
function MonotonicTimestampGenerator(warningThreshold, minLogInterval) {
|
||
|
if (warningThreshold < 0) {
|
||
|
throw new errors.ArgumentError('warningThreshold can not be lower than 0');
|
||
|
}
|
||
|
this._warningThreshold = warningThreshold || 1000;
|
||
|
this._minLogInterval = 1000;
|
||
|
if (typeof minLogInterval === 'number') {
|
||
|
// A value under 1 will disable logging
|
||
|
this._minLogInterval = minLogInterval;
|
||
|
}
|
||
|
this._micros = -1;
|
||
|
this._lastDate = 0;
|
||
|
this._lastLogDate = 0;
|
||
|
}
|
||
|
|
||
|
util.inherits(MonotonicTimestampGenerator, TimestampGenerator);
|
||
|
|
||
|
/**
|
||
|
* Returns the current time in milliseconds since UNIX epoch
|
||
|
* @returns {Number}
|
||
|
*/
|
||
|
MonotonicTimestampGenerator.prototype.getDate = function () {
|
||
|
return Date.now();
|
||
|
};
|
||
|
|
||
|
MonotonicTimestampGenerator.prototype.next = function (client) {
|
||
|
let date = this.getDate();
|
||
|
let drifted = 0;
|
||
|
if (date > this._lastDate) {
|
||
|
this._micros = 0;
|
||
|
this._lastDate = date;
|
||
|
return this._generateMicroseconds();
|
||
|
}
|
||
|
|
||
|
if (date < this._lastDate) {
|
||
|
drifted = this._lastDate - date;
|
||
|
date = this._lastDate;
|
||
|
}
|
||
|
if (++this._micros === 1000) {
|
||
|
this._micros = 0;
|
||
|
if (date === this._lastDate) {
|
||
|
// Move date 1 millisecond into the future
|
||
|
date++;
|
||
|
drifted++;
|
||
|
}
|
||
|
}
|
||
|
const lastDate = this._lastDate;
|
||
|
this._lastDate = date;
|
||
|
const result = this._generateMicroseconds();
|
||
|
if (drifted >= this._warningThreshold) {
|
||
|
// Avoid logging an unbounded amount of times within a clock-skew event or during an interval when more than 1
|
||
|
// query is being issued by microsecond
|
||
|
const currentLogDate = Date.now();
|
||
|
if (this._minLogInterval > 0 && this._lastLogDate + this._minLogInterval <= currentLogDate){
|
||
|
const message = util.format(
|
||
|
'Timestamp generated using current date was %d milliseconds behind the last generated timestamp (which ' +
|
||
|
'millisecond portion was %d), the returned value (%s) is being artificially incremented to guarantee ' +
|
||
|
'monotonicity.',
|
||
|
drifted, lastDate, result);
|
||
|
this._lastLogDate = currentLogDate;
|
||
|
client.log('warning', message);
|
||
|
}
|
||
|
}
|
||
|
return result;
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* @private
|
||
|
* @returns {Number|Long}
|
||
|
*/
|
||
|
MonotonicTimestampGenerator.prototype._generateMicroseconds = function () {
|
||
|
if (this._lastDate < _maxSafeNumberDate) {
|
||
|
// We are safe until Jun 06 2255, its faster to perform this operations on Number than on Long
|
||
|
// We hope to have native int64 by then :)
|
||
|
return this._lastDate * 1000 + this._micros;
|
||
|
}
|
||
|
return Long
|
||
|
.fromNumber(this._lastDate)
|
||
|
.multiply(_longOneThousand)
|
||
|
.add(Long.fromInt(this._micros));
|
||
|
};
|
||
|
|
||
|
exports.TimestampGenerator = TimestampGenerator;
|
||
|
exports.MonotonicTimestampGenerator = MonotonicTimestampGenerator;
|