event_loop.js

// *****************************************************************************
// Copyright 2013-2023 Aerospike, 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 as = require('bindings')('aerospike.node')
const AerospikeError = require('./error')
const CommandQueuePolicy = require('./policies/command_queue_policy')

/**
 * Whether event loop resources have been released
 *
 * @type {boolean}
 * @private
 */
let _eventLoopReleased = false

/**
 * Whether event loop resources have been created
 *
 * @type {boolean}
 * @private
 */
let _eventLoopInitialized = false

let _commandQueuePolicy = new CommandQueuePolicy()

/**
 * @memberof! module:aerospike
 *
 * @summary Release event loop resources held by the module, which could keep
 * the Node.js event loop from shutting down properly.
 *
 * @description This method releases some event loop resources held by the
 * Aerospike module and the Aerospike C client library, such as libuv handles
 * and timers. If not released, these handles will prevent the Node.js event
 * loop from shutting down, i.e. it will keep your application from
 * terminating.
 *
 * The Aerospike module keeps an internal counter of active {@link Client}
 * instances, i.e. instances which have not been <code>close()</code>'d yet. If
 * a client is closed and the counter reaches zero, this method will be called
 * automatically, unless {@link Client#close} is called with
 * <code>releaseEventLoop</code> set to <code>false</code>. (The default is
 * <code>true</code>.)
 *
 * If an application needs to create multiple client instance, i.e. to connect
 * to multiple, different clusters, the event loop resources will be managed
 * automatically, as long as at least once client instance is active at any
 * given time, until the application terminates.
 *
 * If, however, there could be one or more intermittent time periods, during
 * which no client is active (i.e. the internal client counter reaches zero),
 * then the clients need to be closed with <code>releaseEventLoop</code> set
 * to <code>false</code> and the event loop needs to be released explicitly by
 * calling <code>releaseEventLoop()</code>.
 */
function releaseEventLoop () {
  if (_eventLoopReleased) return
  if (as.get_cluster_count() > 0) {
    setTimeout(releaseEventLoop, 5)
  } else {
    as.release_as_event_loop()
    _eventLoopReleased = true
  }
}

/**
 * @private
 */
function registerASEventLoop () {
  if (_eventLoopReleased) {
    throw new AerospikeError('Event loop resources have already been released! Call Client#close() with releaseEventLoop set to false to avoid this error.')
  }

  if (_eventLoopInitialized) {
    referenceEventLoop()
  } else {
    as.register_as_event_loop(_commandQueuePolicy)
    _eventLoopInitialized = true
  }
}

/**
 * @private
 */
function referenceEventLoop () {
  if (!_eventLoopInitialized || _eventLoopReleased) {
    throw new AerospikeError('Event loop is not initialized right now')
  }

  as.ref_as_event_loop()
}

/**
 * @private
 */
function unreferenceEventLoop () {
  if (!_eventLoopInitialized || _eventLoopReleased) {
    throw new AerospikeError('Event loop is not initialized right now')
  }

  as.unref_as_event_loop()
}

/**
 * @private
 */
function setCommandQueuePolicy (policy) {
  if (_eventLoopInitialized) {
    throw new AerospikeError('Command queue has already been initialized! Call Aerospike#setGlobalCommandQueuePolicy() before connecting any client instances.')
  }
  _commandQueuePolicy = policy
}

module.exports = {
  releaseEventLoop,
  registerASEventLoop,
  referenceEventLoop,
  unreferenceEventLoop,
  setCommandQueuePolicy
}