setImmediate

Let’s take a look at what happens when we use setImmediate

So let’s do it this way

setImmediate(fn, arg)Copy the code

SetImmediate accepts callback, Arg1, and several other parameters

exports.setImmediate = function(callback, arg1, arg2, arg3) {
  if (typeofcallback ! = ='function') {
    throw new TypeError('"callback" argument must be a function');
  }

  var i, args;

    // Determine the number of arguments passed
  switch (arguments.length) {
       // If only callback takes no other arguments, exit the switch immediately
    // fast cases
    case 1:
      break;
    case 2:
        // If there is only one argument, set 'args' to have an array containing one argument
      args = [arg1];
      break;
    case 3:
      args = [arg1, arg2];
      break;
    default:
        // If the length of the argument exceeds 4, fill the argument after traversal with 'args'
      args = [arg1, arg2, arg3];
      for (i = 4; i < arguments.length; i++)
            // It is also mentioned here that using 'apply' after Node 6.0.0 will be much faster than the current dynamic array extension
        // extend array dynamically, makes .apply run much faster in v6.0.0
        args[i - 1] = arguments[i];
      break;
  }
  // Start creating 'Immediate'
  return createImmediate(args, callback);
};Copy the code

The first step is to determine and wrap the parameters. Here you create the Immediate

createImmediate


function createImmediate(args, callback) {
  // Const immediate 'cannot be optimized in 6.0.0
  // Create the 'Immediate' node and assign parameters to the node. Note that '_callback' and '_onImmediate' both assign 'callback'

  var immediate = new Immediate();
  immediate._callback = callback;
  immediate._argv = args;
  immediate._onImmediate = callback;

    // Set the 'process._needImmediateCallback' flag and assign 'processImmediate' to 'process._immediateCallback' for native module calls
  if(! process._needImmediateCallback) { process._needImmediateCallback =true;
    process._immediateCallback = processImmediate;
  }

  // 'immediateQueue' Add the IMMEDIATE node to the immediateQueue list
  immediateQueue.append(immediate);

  return immediate;
}Copy the code

The createImmediate creates immediate based on the received parameters and adds it to the immediateQueue queue, setting flags in the thread for the immediate callback.

Immediate queue node

Constructor of the Immediate task queue node used here. The ImmediateQueue used here was an unordered linked list.

function Immediate() {
    // Register callback directly and the optimization will be unstable.
    // Is this a problem for hidden Class?
  this._idleNext = null;
  this._idlePrev = null;
  this._callback = null;
  this._argv = null;
  this._onImmediate = null;
  // Set to the domain of the current thread
  this.domain = process.domain;
}Copy the code

processImmediate

function processImmediate() {
    // take the beginning and end of the queue and declare 'domain'
  var immediate = immediateQueue.head;
  var tail = immediateQueue.tail;
  var domain;

  // Clear the queue head and tail
  immediateQueue.head = immediateQueue.tail = null;

  while (immediate) {
       // Domain of the immediate task
    domain = immediate.domain;

    // If there is no callback, the next one
    if(! immediate._onImmediate) { immediate = immediate._idleNext;continue;
    }

    if (domain)
      domain.enter();
    // Callback = 'callback' 😯
    immediate._callback = immediate._onImmediate;

    // Save the next node to avoid clearing when 'clearImmediate(immediate)' is called.
    var next = immediate._idleNext;

    tryOnImmediate(immediate, tail);

    if (domain)
      domain.exit();

    // If 'clearImmediate(immediate)' is called, use the previously stored next; if not, call 'immediate._idleNext'
    if (immediate._idleNext)
      immediate = immediate._idleNext;
    else
      immediate = next;
  }

  // If the immediate queue is empty, set '_needImmediateCallback' to false
  // it should be mentioned that there is an implementation in the logical C++ module
  if(! immediateQueue.head) { process._needImmediateCallback =false; }}Copy the code

The main function of processImmediate is to iterate through the nodes in the immediateQueue and call tryOnImmediate to try to perform the task.

You can see that it was set in the _immediateCallback of process. So there is a question, when is it called to execute?

You can see here that the _immediateCallback proxy is set on the env global environment variable

// src/env.h

V(immediate_callback_string, "_immediateCallback")  

  static inline Environment* from_immediate_check_handle(uv_check_t* handle);
  static inline Environment* from_destroy_ids_idle_handle(uv_idle_t* handle);
  inline uv_check_t* immediate_check_handle(a);
  inline uv_idle_t* immediate_idle_handle(a);
  inline uv_idle_t* destroy_ids_idle_handle(a);Copy the code
// src/node.cc

static void CheckImmediate(uv_check_t* handle) {
  Environment* env = Environment::from_immediate_check_handle(handle);
  HandleScope scope(env->isolate());
  Context::Scope context_scope(env->context());
  MakeCallback(env, env->process_object(), env->immediate_callback_string());
}Copy the code

See here CheckImmediate feeling is close to the answer.

tryOnImmediate

Let’s go back to JS

function tryOnImmediate(immediate, oldTail) {
  var threw = true;
  try {
    // This is because the previous V8 would not optimize functions with 'try/finally', so small function is optimized by externalizing it to a small function
    runCallback(immediate);
    threw = false;
  } finally {
      // If the execution succeeds and there is a next node
    if (threw && immediate._idleNext) {
      // Proceed to the next one
      const curHead = immediateQueue.head;
      const next = immediate._idleNext;

      if (curHead) {
        curHead._idlePrev = oldTail;
        oldTail._idleNext = curHead;
        next._idlePrev = null;
        immediateQueue.head = next;
      } else {
        immediateQueue.head = next;
        immediateQueue.tail = oldTail;
      }
      // Continue processing the Immediate task queue in the next event loopprocess.nextTick(processImmediate); }}}Copy the code

In order to obtain v8 optimized tryOnImmediate, the callback of the execution node is placed in the runCallback small function in the try/finally.

runCallback

function runCallback(timer) {
  const argv = timer._argv;
  const argc = argv ? argv.length : 0;
  switch (argc) {
    // Here you can go back to the creation of arguments from above
    case 0:
      return timer._callback();
    case 1:
      return timer._callback(argv[0]);
    case 2:
      return timer._callback(argv[0], argv[1]);
    case 3:
      return timer._callback(argv[0], argv[1], argv[2]);
    // more than 3 arguments run slower with .apply
    default:
      returntimer._callback.apply(timer, argv); }}Copy the code

Seems to have finally finished the setImmediate creation handling section 👀

setTimeout

The parameter handling here is very similar to the setImmediate parameter handling

exports.setTimeout = function(callback, after, arg1, arg2, arg3) { if (typeof callback ! == 'function') { throw new TypeError('"callback" argument must be a function'); } var len = arguments.length; var args; if (len === 3) { args = [arg1]; } else if (len === 4) { args = [arg1, arg2]; } else if (len > 4) { args = [arg1, arg2, arg3]; for (var i = 5; i < len; i++) args[i - 2] = arguments[i]; } return createSingleTimeout(callback, after, args); };Copy the code

createSingleTimeout

This is starting to look a little bit different, so let’s keep looking at the code

function createSingleTimeout(callback, after, args) {
    // Try to convert to Number or NaN
  after *= 1;
  // If after is less than 1 or after > TIMEOUT_MAX
  // after = 1
  if(! (after >=1 && after <= TIMEOUT_MAX))
    after = 1;

    // Create a new Timeout queue node with the parameters
  var timer = new Timeout(after, callback, args);
  if (process.domain)
    timer.domain = process.domain;

  // Join the Timeout queue
  active(timer);

  return timer;
}Copy the code

const TIMEOUT_MAX = 2147483647; / / 2 ^ 31-1

As a bonus, the TIMEOUT_MAX value is 2^31-1, which means that we can delay execution by setTimeout for about 2147483647 ms, or about 24 days.

Constructor of the Timeout node

function Timeout(after, callback, args) {
  this._called = false;
  this._idleTimeout = after;
  this._idlePrev = this;
  this._idleNext = this;
  this._idleStart = null;
  this._onTimeout = callback;
  this._timerArgs = args;
  // This will be associated with setInterval
  this._repeat = null;
}Copy the code

Insert the timeout timer into the timer list

In this algorithm, timeout tasks of the same MS level share a timeWrap, and tasks of the same time are assigned to the same linked list, so that the scheduling and newly added complexity of timing tasks are O(1), and the same timeWrap is efficiently reused.

const active = exports.active = function(item) {
  insert(item, false);
};

// The underlying logic for scheduling or rescheduling timers
// Timers will be added to the end of an existing timer list, or a new list will be created

function insert(item, unrefed) {
  const msecs = item._idleTimeout;
  if (msecs < 0 || msecs === undefined) return;

    // TimerWrap is the native module timer_wrap
  item._idleStart = TimerWrap.now();

  const lists = unrefed === true ? unrefedLists : refedLists;

    // Create or use an existing queue
  var list = lists[msecs];
  if(! list) { debug('no %d list was found in insert, creating a new one', msecs); lists[msecs] = list = createTimersList(msecs, unrefed); } L.append(list, item); assert(! L.isEmpty(list));// list is not empty
}Copy the code

Create a timeout timer list

function createTimersList (msecs, unrefed) {
    // Create a new list and create an instance of TimerWrap to schedule the list
  const list = new TimersList(msecs, unrefed);
  L.init(list);
  list._timer._list = list;

  if (unrefed === true) list._timer.unref();
  list._timer.start(msecs);

  list._timer[kOnTimeout] = listOnTimeout;

  return list;
}Copy the code

TimersList

The difference between this list node and Immediate is this._timer = new TimerWrap(), where a new TimerWrap instance is created.

function TimersList (msecs, unrefed) {
  this._idleNext = null; // Create the list with the linkedlist properties to
  this._idlePrev = null; // prevent any unnecessary hidden class changes.
  this._timer = new TimerWrap();
  this._unrefed = unrefed;
  this.msecs = msecs;
  this.nextTick = false;
}Copy the code

TimerWrap

TimerWrap is a class in Nodejs, implemented in/SRC /timer_wrap.cc. It is a wrapper around uv_timer_t, a brige that connects JavaScript to libuv.

Let’s start with this example to see what TimerWrap can do.


const TimerWrap = process.binding('timer_wrap').Timer
const kOnTimeout = TimerWrap.kOnTimeout | 0

let timer = new TimerWrap();
timer.start(2333);

console.log('started');

timer[kOnTimeout] = function () {
  console.log('2333! '); }; Output: started2333 / / after 2.333 sCopy the code

In libuv’s uv_timer_t implementation, the data structure of the minimum heap is used, and the minimum judgment of the node is based on its timeout. If the timeout is the same, the start_id of the two nodes is determined, and the start_id is an increasing node count. This ensures the call timing.

// deps/uv/src/unix/timer.c

static int timer_less_than(const struct heap_node* ha,
                           const struct heap_node* hb) {
  const uv_timer_t* a;
  const uv_timer_t* b;

  a = container_of(ha, uv_timer_t, heap_node);
  b = container_of(hb, uv_timer_t, heap_node);

  if (a->timeout < b->timeout)
    return 1;
  if (b->timeout < a->timeout)
    return 0;

  /* Compare start_id when both have the same timeout. start_id is * allocated with loop->timer_counter in uv_timer_start(). */
  if (a->start_id < b->start_id)
    return 1;
  if (b->start_id < a->start_id)
    return 0;

  return 0;
}Copy the code

TimerWrap source

TimerWrap acts as a birge connecting to Libuv, so it’s easy to see uv_timer_start being called in the Start method, passing its own pointer, the second argument being callback, and the third argument being timeout.

Moving on to OnTimeout, its main job is to call a callback with key kOnTimeout, which triggers our JavaScript layer’s callback function.

// src/timer_wrap.cc
class TimerWrap : public HandleWrap {
...
private:
  static void Start(const FunctionCallbackInfo<Value>& args) {
    TimerWrap* wrap = Unwrap<TimerWrap>(args.Holder());

    CHECK(HandleWrap::IsAlive(wrap));

    int64_t timeout = args[0]->IntegerValue();
    int err = uv_timer_start(&wrap->handle_, OnTimeout, timeout, 0);
    args.GetReturnValue().Set(err);
  }

  static void OnTimeout(uv_timer_t* handle) {
    TimerWrap* wrap = static_cast<TimerWrap*>(handle->data);
    Environment* env = wrap->env();
    HandleScope handle_scope(env->isolate());
    Context::Scope context_scope(env->context());
    wrap->MakeCallback(kOnTimeout, 0, nullptr);
  }Copy the code

Let’s go back to the createTimersList, the TimerWrap that we introduced briefly, and now we’re happy to move on.

function createTimersList (msecs, unrefed) {
    // Create a new list and create an instance of TimerWrap to schedule the list
  const list = new TimersList(msecs, unrefed);
  L.init(list);
  list._timer._list = list;

  if (unrefed === true) list._timer.unref();

  // Set delay here
  list._timer.start(msecs);

    // Set the delay callback function here. Next, go to 👉 listOnTimeout
  list._timer[kOnTimeout] = listOnTimeout;

  return list;
}Copy the code

listOnTimeout

The trick here is similar to processImmediate

function listOnTimeout() {
  var list = this._list;
  var msecs = list.msecs;

  // If list.nextTick is true, the next event loop call to listOnTimeoutNT executes immediately
  if (list.nextTick) {
    list.nextTick = false;
    process.nextTick(listOnTimeoutNT, list);
    return;
  }

  debug('timeout callback %d', msecs);

    // Get the current running time
  var now = TimerWrap.now();
  debug('now: %d', now);

  var diff, timer;
  while (timer = L.peek(list)) {
    diff = now - timer._idleStart;
    // Determine if the loop here is called too soon
    if (diff < msecs) {
      var timeRemaining = msecs - (TimerWrap.now() - timer._idleStart);
      if (timeRemaining < 0) {
        timeRemaining = 0;
      }
      this.start(timeRemaining);
      debug('%d list wait because diff is %d', msecs, diff);
      return;
    }

     // Start the timeout logic

     // Remove the current timer node from the linked list
    L.remove(timer);
    // Check if it is removed from the listassert(timer ! == L.peek(list));// If there is no callback function, skip to the next loop
    if(! timer._onTimeout)continue;

    var domain = timer.domain;
    if (domain) {
        // If the counter callback throws an error, domain and uncaughtException ignore the exception and the other timers execute normally
      // https://github.com/nodejs/node-v0.x-archive/issues/2631

      if (domain._disposed)
        continue;

      domain.enter();
    }

    tryOnTimeout(timer, list);

    if (domain)
      domain.exit();
  }

  // The timer has been completely called and the linked list has been cleared. Call TimerWrap's close for cleanup
  debug('%d list empty', msecs);
  assert(L.isEmpty(list));
  this.close();

  // Either refedLists[msecs] or unrefedLists[msecs] may have been removed and
  // recreated since the reference to `list` was created. Make sure they're
  // the same instance of the list before destroying.

  / / clean up
  if (list._unrefed === true && list === unrefedLists[msecs]) {
    delete unrefedLists[msecs];
  } else if (list === refedLists[msecs]) {
    deleterefedLists[msecs]; }}Copy the code

tryOnTimeout

TryOnTimeout is handled in much the same way as tryOnImmediate

// As with tryOnImmediate, v8 optimizations are also considered, so small function is used to implement the timer

function tryOnTimeout(timer, list) {
  timer._called = true;
  var threw = true;
  try {
    ontimeout(timer);
    threw = false;
  } finally {
    // If no error is thrown, end immediately
    if(! threw)return;
     // Throw an error when it is not executed properly
     // Defer all events in the list to the next cycle to ensure execution order.

    const lists = list._unrefed === true ? unrefedLists : refedLists;

    for (var key in lists) {
      if (key > list.msecs) {
        lists[key].nextTick = true; }}// We need to continue processing after domain error handling
    // is complete, but not by using whatever domain was left over
    // when the timeout threw its exception.

    const domain = process.domain;
    process.domain = null;

    // If an error is thrown, the next counter callback is performed in nextTickprocess.nextTick(listOnTimeoutNT, list); process.domain = domain; }}Copy the code

ontimeout

function ontimeout(timer) {
  var args = timer._timerArgs;
  var callback = timer._onTimeout;
  if(! args) callback.call(timer);else {
    switch (args.length) {
      case 1:
        callback.call(timer, args[0]);
        break;
      case 2:
        callback.call(timer, args[0], args[1]);
        break;
      case 3:
        callback.call(timer, args[0], args[1], args[2]);
        break;
      default: callback.apply(timer, args); }}// This is the implementation of setInterval, more on that later
  if (timer._repeat)
    rearm(timer);
}Copy the code

setInterval

The implementation here is pretty much the same as setTimeout, setImmediate.

exports.setInterval = function(callback, repeat, arg1, arg2, arg3) {
  if (typeofcallback ! = ='function') {
    throw new TypeError('"callback" argument must be a function');
  }

  var len = arguments.length;
  var args;
  if (len === 3) {
    args = [arg1];
  } else if (len === 4) {
    args = [arg1, arg2];
  } else if (len > 4) {
    args = [arg1, arg2, arg3];
    for (var i = 5; i < len; i++)
      // extend array dynamically, makes .apply run much faster in v6.0.0
      args[i - 2] = arguments[i];
  }

  return createRepeatTimeout(callback, repeat, args);
};Copy the code

Interval === repeat timeout?

The implementation of setInterval differs from setTimeout in timer._repeat = repeat

function createRepeatTimeout(callback, repeat, args) {
  repeat *= 1; // coalesce to number or NaN
  if(! (repeat >=1 && repeat <= TIMEOUT_MAX))
    repeat = 1; // schedule on next tick, follows browser behaviour

  var timer = new Timeout(repeat, callback, args);
  timer._repeat = repeat;
  if (process.domain)
    timer.domain = process.domain;

  active(timer);
  return timer;
}Copy the code

clear

After looking at the methods used to create three time schedules, look at the code for cleaning up the timer.

clearImmediate

exports.clearImmediate = function(immediate) {
  if(! immediate)return;

  immediate._onImmediate = null;

  immediateQueue.remove(immediate);

  if(! immediateQueue.head) { process._needImmediateCallback =false; }};Copy the code

clearTimeout

const clearTimeout = exports.clearTimeout = function(timer) {
  if (timer && (timer[kOnTimeout] || timer._onTimeout)) {
    timer[kOnTimeout] = timer._onTimeout = null;
    if (timer instanceof Timeout) {
      timer.close(); // for after === 0
    } else{ unenroll(timer); }}};Copy the code

Timeout.unref

The timer here provides close, unref, and ref methods, in which ref and unref call the underlying uv_ref() and uv_unref() through TimerWrap.

It is mentioned in the official Nodejs documentation

When called, the active Timeout object will not require the Node.js event loop to remain active. If there is no other activity keeping the event loop running, the process may exit before the Timeout object’s callback is invoked.

Calling unref() actively, if there are no other active objects, may cause the Nodejs event loop to exit prematurely

Timeout.prototype.unref = function() {
  if (this._handle) {
    this._handle.unref();
  } else if (typeof this._onTimeout === 'function') {
    var now = TimerWrap.now();
    if (!this._idleStart) this._idleStart = now;
    var delay = this._idleStart + this._idleTimeout - now;
    if (delay < 0) delay = 0;

    // Prevent the callback from running again after calling 'unref()'
    if (this._called && !this._repeat) {
      unenroll(this);
      return;
    }

    var handle = reuse(this);

    this._handle = handle || new TimerWrap();
    this._handle.owner = this;
    this._handle[kOnTimeout] = unrefdHandle;
    this._handle.start(delay);
    this._handle.domain = this.domain;
    this._handle.unref();
  }
  return this;
};Copy the code

Timeout.ref

Timeout.prototype.ref = function() {
  if (this._handle)
    this._handle.ref();
  return this;
};Copy the code

Timeout.close

Timeout.prototype.close = function() {
  this._onTimeout = null;
  if (this._handle) {
    this._idleTimeout = - 1;
    this._handle[kOnTimeout] = null;
    this._handle.close();
  } else {
    unenroll(this);
  }
  return this;
};


// Remove timer, cancel delay and reset timer properties
const unenroll = exports.unenroll = function(item) {
  var handle = reuse(item);
  if (handle) {
    debug('unenroll: list empty');
    handle.close();
  }
  // Ensure that the queue is not inserted later
  item._idleTimeout = - 1;
};


// a simple conversion function to reuse TimerWrap
//
// This mostly exists to fix https://github.com/nodejs/node/issues/1264.
// Handles in libuv take at least one `uv_run` to be registered as unreferenced.
// Re-using an existing handle allows us to skip that, so that a second `uv_run`
// will return no active handles, even when running `setTimeout(fn).unref()`.

function reuse(item) {
  L.remove(item);

  var list = refedLists[item._idleTimeout];
  // if empty - reuse the watcher
  if (list && L.isEmpty(list)) {
    debug('reuse hit');
    list._timer.stop();
    delete refedLists[item._idleTimeout];
    return list._timer;
  }

  return null;
}Copy the code

clearInterval

exports.clearInterval = function(timer) {
  if (timer && timer._repeat) {
    timer._repeat = null; clearTimeout(timer); }};Copy the code

The ending 💊

The above first

┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐ ┌ ─ > │ timers │ │ └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┬ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘ │ ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┴ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐ │ │ I/O callbacks │ │ └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┬ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘ │ ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┴ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐ │ │ idle, Prepare │ │ └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┬ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘ ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐ │ ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┴ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐ │ incoming: │ │ │ poll │ < ─ ─ ─ ─ ─ ┤ connections, │ │ └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┬ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘ │ data, Etc. │ │ ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┴ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐ └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘ │ │ check │ │ └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┬ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘ │ ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┴ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐ ├ ──┤ close callbacks ────── our r companyCopy the code

SetImmediate Is typically used in the Check phase, but setTimeout may be used in the poll phase. SetInterval is used in the Timer phase

SetTimeout (fn, 0) setImmediate(fn)

setTimeout(console.log, 0.1);
setImmediate(console.log, 2);

// If each stage is idle, each event loop is less than 1ms:
2
1

// It can also be over 1ms

1
2Copy the code

If called within an I/O loop, immediate is always executed before setTimeout. Because immediate is executed immediately after poll completes in the Event loop, setTimeout goes to the next timers stage.

var fs = require('fs')

fs.readFile(__filename, () => {
    setTimeout(console.log, 0.1);
    setImmediate(console.log, 2);
})

/ / output:
2
1Copy the code

One more

If we write this in Nodejs, what will the output be?

var a = setTimeout(console.log, 50.2333);
a._repeat = true;Copy the code

Like this?

var a = setTimeout(console.log, 1000.2333);
a.close()Copy the code

Like this?

var a = setTimeout(console.log, 1000.2333);
a.unref()Copy the code

References:

node/lib/timers.js

node/lib/internal/linkedlist.js

node/src/timer_wrap.cc

event-loop-timers-and-nexttick

Optimizing _unrefActive