/* * 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 to null, 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;