All files otp.js

100% Statements 7/7
66.67% Branches 8/12
100% Functions 0/0
100% Lines 6/6
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110                              1x 1x   1x 1x     1x                 6x                                                                                                                                                            
/**
 * OTP
 *
 * Docs {@link http://erlang.org/doc/design_principles/des_princ.html Here}.
 * @namespace OTP
 *
 */
 
/* eslint-disable no-continue */
 
import { delay } from 'redux-saga';
import { fork, race, call, cancel, join } from 'redux-saga/effects';
import { prop, values, map, has, head, compose, reject, merge } from 'ramda';
 
 
export const ONE_FOR_ONE = 'ONE_FOR_ONE',
  ONE_FOR_MANY = 'ONE_FOR_MANY',
 
  PERMANENT = 'PERMANENT',
  TEMPORARY = 'TEMPORARY';
 
 
const debug = require('debug');
 
/**
 * Supervisor OTP behavior
 *
 * Docs {@link http://erlang.org/documentation/doc-4.9.1/doc/design_principles/sup_princ.html Here}.
 * @memberof OTP
 *
 */
export const supervisor = {
  debug: debug('otp/supervisor'),
 
  *start_link(task, id) {
    supervisor.debug('start_link', { task, id });
    yield call(task);
    return id;
  },
 
  *init({
    supFlags = { },
    childSpecs = []
  }) {
    const { strategy = ONE_FOR_ONE, maxR = 1, maxT = 1000 } = supFlags;
 
    supervisor.debug('init', { supFlags, childSpecs });
    const children = {};
 
    for (const child of childSpecs) {
      const { id, start, restart = PERMANENT } = child;
 
      // fork worker
      const task = yield fork(start);
      supervisor.debug('init: forked', id);
 
      children[id] = { id, task, start, restart, terminated: false };
    }
 
    while (true) {
      const tasks = compose(
        map(join),
        map(prop('task')),
        reject(prop('terminated'))
      )(children);
 
      supervisor.debug('init: loop', { tasks });
 
      const waitedLongEnough = yield race(merge(tasks,
        { waitedLongEnough : call(delay, maxT / maxR, true) }
      ));
 
      if (!has('waitedLongEnough', waitedLongEnough)) {
        supervisor.debug('init: not waited long enough', { waitedLongEnough });
        throw new Error('Restart frequency limit reached');
      }
 
      const terminatedId = head(values(yield race(tasks)));
 
      supervisor.debug('init: %s terminated', terminatedId);
 
 
      if (strategy === ONE_FOR_MANY) {
        // restart all workers
        for (const child of values(children)) {
          if (child.id !== terminatedId) {
            yield cancel(child.task);
          }
 
          if (child.restart === PERMANENT) {
            children[child.id].task = yield fork(child.start);
          } else {
            children[child.id].terminated = true;
          }
        }
        continue;
      } else {
        // restart a worker
        if (children[terminatedId].restart === PERMANENT) {
          children[terminatedId].task = yield fork(children[terminatedId].start);
        } else {
          children[terminatedId].terminated = true;
        }
        continue;
      }
    }
  }
};