/* * 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. *
* Given that Cassandra uses those timestamps to resolve conflicts, implementations should generate * monotonically increasing timestamps for successive invocations of {@link TimestampGenerator.next()}. *
* @constructor */ function TimestampGenerator() { } /** * Returns the next timestamp. ** Implementors should enforce increasing monotonicity of timestamps, that is, * a timestamp returned should always be strictly greater that any previously returned * timestamp. *
** Implementors should strive to achieve microsecond precision in the best possible way, * which is usually largely dependent on the underlying operating system's capabilities. *
* @param {Client} client The {@link Client} instance to generate timestamps to. * @returns {Long|Number|null} the next timestamp (in microseconds). If it's equals tonull
, 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.
* * {@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. *
* @param {Number} [warningThreshold] Determines how far in the future timestamps are allowed to drift before a * warning is logged, expressed in milliseconds. Default:1000
.
* @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: 1000
.
* @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;