mutual
Version: 
Scala-inspired Actors that use Redis as a message transport
465 lines (438 loc) • 16.8 kB
JavaScript
// Generated by CoffeeScript 1.12.7
(function() {
  var DurableChannel, EventChannel, Redis, RemoteQueue, Transport, randomKey,
    extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
    hasProp = {}.hasOwnProperty;
  randomKey = require("key-forge").randomKey;
  Redis = require("pirate").Redis;
  EventChannel = require("./event-channel");
  RemoteQueue = require("./remote-queue");
  Transport = require("./redis-transport");
  DurableChannel = (function(superClass) {
    extend(DurableChannel, superClass);
    function DurableChannel(options) {
      DurableChannel.__super__.constructor.apply(this, arguments);
      this.ending = false;
      this.name = options.name, this.timeoutMonitorFrequency = options.timeoutMonitorFrequency, this.transport = options.transport;
      if (this.name == null) {
        throw new Error("Durable channels cannot be anonymous");
      }
      this.timeoutMonitor = null;
      if (this.timeoutMonitorFrequency == null) {
        this.timeoutMonitorFrequency = 1000;
      }
      this.events = new EventChannel;
      this.isTransportShared = true;
      if (this.transport == null) {
        this.isTransportShared = false;
        this.transport = new Transport({
          host: options.redis.host,
          port: options.redis.port
        });
        this.transport.events.forward(this.events);
      }
      this.queue = new RemoteQueue({
        name: this.name + ".queue",
        transport: this.transport
      });
      this.monitorTimeouts();
      setImmediate((function(_this) {
        return function() {
          return _this.fire({
            event: "ready"
          });
        };
      })(this));
    }
    DurableChannel.prototype["package"] = function(arg) {
      var content, message, requestId, timeout, to;
      content = arg.content, to = arg.to, requestId = arg.requestId, timeout = arg.timeout;
      return message = {
        id: randomKey(16),
        requestId: requestId,
        from: this.name,
        to: to,
        timeout: timeout,
        content: content
      };
    };
    DurableChannel.prototype.getMessage = function(channel, id) {
      return this.events.source((function(_this) {
        return function(events) {
          return _this.transport._acquire(function(client) {
            return client.hget(channel + ".messages", id, function(err, data) {
              _this.transport._release(client);
              return events.callback(err, data != null ? JSON.parse(data) : null);
            });
          });
        };
      })(this));
    };
    DurableChannel.prototype.putMessage = function(channel, id, message) {
      return this.events.source((function(_this) {
        return function(events) {
          return _this.transport._acquire(function(client) {
            return client.hset(channel + ".messages", id, JSON.stringify(message), function(err, data) {
              _this.transport._release(client);
              return events.callback(err, data);
            });
          });
        };
      })(this));
    };
    DurableChannel.prototype.deleteMessage = function(channel, id) {
      return this.events.source((function(_this) {
        return function(events) {
          return _this.transport._acquire(function(client) {
            return client.hdel(channel + ".messages", id, function(err, data) {
              _this.transport._release(client);
              return events.callback(err, data);
            });
          });
        };
      })(this));
    };
    DurableChannel.prototype.getDestinationQueue = function(name) {
      var queue;
      return queue = new RemoteQueue({
        name: name + ".queue",
        transport: this.transport
      });
    };
    DurableChannel.prototype.setMessageTimeout = function(name, channel, id, timeout) {
      var client, serverTime;
      if ((channel != null) && (id != null) && (timeout != null)) {
        client = null;
        serverTime = 0;
        return this.events.serially((function(_this) {
          return function(go) {
            go(function() {
              return _this.events.source(function(events) {
                return _this.transport._acquire(function(_client) {
                  client = _client;
                  return events.callback();
                });
              });
            });
            go(function() {
              return _this.events.source(function(events) {
                return client.time(function(err, data) {
                  if (err != null) {
                    _this.transport._release(client);
                  } else {
                    serverTime = data[0] * 1000;
                  }
                  return events.callback(err, data);
                });
              });
            });
            return go(function() {
              return _this.events.source(function(events) {
                return client.zadd([name + ".pending", serverTime + timeout, channel + "::" + id], function(err, data) {
                  _this.transport._release(client);
                  return events.callback(err, data);
                });
              });
            });
          };
        })(this))();
      }
    };
    DurableChannel.prototype.clearMessageTimeout = function(name, channel, id) {
      if (id != null) {
        return this.events.source((function(_this) {
          return function(events) {
            return _this.transport._acquire(function(client) {
              return client.zrem([name + ".pending", channel + "::" + id], function(err, data) {
                _this.transport._release(client);
                return events.callback(err, data);
              });
            });
          };
        })(this));
      }
    };
    DurableChannel.prototype.getMessageTimeout = function(name, channel, id) {
      if (id != null) {
        return this.events.source((function(_this) {
          return function(events) {
            return _this.transport._acquire(function(client) {
              return client.zscore([name + ".pending", channel + "::" + id], function(err, data) {
                _this.transport._release(client);
                return events.callback(err, data);
              });
            });
          };
        })(this));
      }
    };
    DurableChannel.prototype.monitorTimeouts = function() {
      var loopToMonitor;
      loopToMonitor = (function(_this) {
        return function() {
          return _this.events.serially(function(go) {
            var client, serverTime;
            client = null;
            serverTime = 0;
            go(function() {
              return _this.events.source(function(events) {
                return _this.transport._acquire(function(_client) {
                  client = _client;
                  return events.callback();
                });
              });
            });
            go(function() {
              return _this.events.source(function(events) {
                return client.time(function(err, data) {
                  if (err != null) {
                    _this.transport._release(client);
                  } else {
                    serverTime = data[0] * 1000;
                  }
                  return events.callback(err, data);
                });
              });
            });
            go(function() {
              return _this.events.source(function(events) {
                return client.zrangebyscore([_this.name + ".pending", 0, serverTime], function(err, data) {
                  _this.transport._release(client);
                  return events.callback(err, data);
                });
              });
            });
            go(function(expiredMessages) {
              if (expiredMessages.length === 0) {
                return;
              }
              return _this.events.source(function(events) {
                var iterate, iterationCount;
                iterationCount = 0;
                return (iterate = function() {
                  var _events, expiredMessageTokens;
                  if (iterationCount < expiredMessages.length) {
                    expiredMessageTokens = expiredMessages[iterationCount].split("::");
                    iterationCount++;
                    _events = _this.expireMessage(expiredMessageTokens[0], expiredMessageTokens[1]);
                    _events.on("success", function() {
                      return iterate();
                    });
                    return _events.on("error", function() {
                      return iterate();
                    });
                  } else {
                    return events.emit("success");
                  }
                })();
              });
            });
            return go(function() {
              if (!_this.ending) {
                return _this.timeoutMonitor = setTimeout(loopToMonitor, _this.timeoutMonitorFrequency);
              }
            });
          })();
        };
      })(this);
      return this.timeoutMonitor = setTimeout(loopToMonitor, this.timeoutMonitorFrequency);
    };
    DurableChannel.prototype.expireMessage = function(channel, id) {
      var message;
      message = null;
      return this.events.serially((function(_this) {
        return function(go) {
          go(function() {
            return _this.getMessage(channel, id);
          });
          go(function(_message) {
            message = _message;
            return _this.getMessageTimeout(_this.name, channel, id);
          });
          return go(function(timeout) {
            if (timeout == null) {
              return;
            }
            return _this.events.serially(function(go) {
              go(function() {
                if (message != null) {
                  return _this.deleteMessage(channel, id);
                }
              });
              go(function() {
                return _this.clearMessageTimeout(_this.name, channel, id);
              });
              return go(function() {
                if (message != null) {
                  return _this.fire({
                    event: "timeout",
                    content: {
                      content: message.content,
                      requestId: message.requestId
                    }
                  });
                }
              });
            })();
          });
        };
      })(this))();
    };
    DurableChannel.prototype.send = function(arg) {
      var content, message, timeout, to;
      content = arg.content, to = arg.to, timeout = arg.timeout;
      message = this["package"]({
        content: content,
        to: to,
        timeout: timeout
      });
      return this.events.serially((function(_this) {
        return function(go) {
          go(function() {
            return _this.putMessage(to, message.id, message);
          });
          go(function() {
            return _this.setMessageTimeout(_this.name, to, message.id, message.timeout);
          });
          return go(function() {
            return _this.getDestinationQueue(to).emit("message", message.id);
          });
        };
      })(this))();
    };
    DurableChannel.prototype.reply = function(arg) {
      var message, response, timeout;
      message = arg.message, response = arg.response, timeout = arg.timeout;
      return this.events.serially((function(_this) {
        return function(go) {
          go(function() {
            return _this.getMessage(_this.name, message.requestId);
          });
          return go(function(request) {
            if (request == null) {
              return null;
            }
            message = _this["package"]({
              content: response,
              to: request.from,
              requestId: message.requestId,
              timeout: timeout
            });
            return _this.events.serially(function(go) {
              go(function() {
                return _this.clearMessageTimeout(request.from, _this.name, message.requestId);
              });
              go(function() {
                return _this.putMessage(request.from, message.id, message);
              });
              go(function() {
                return _this.setMessageTimeout(_this.name, request.from, message.id, message.timeout);
              });
              return go(function() {
                return _this.getDestinationQueue(request.from).emit("message", message.id);
              });
            })();
          });
        };
      })(this))();
    };
    DurableChannel.prototype.close = function(message) {
      return this.events.serially((function(_this) {
        return function(go) {
          go(function() {
            return _this.deleteMessage(_this.name, message.responseId);
          });
          return go(function() {
            return _this.clearMessageTimeout(message.to, message.from, message.responseId);
          });
        };
      })(this))();
    };
    DurableChannel.prototype.listen = function() {
      return this.events.source((function(_this) {
        return function(events) {
          return _this.queue.listen().on("success", function() {
            var messageHandler;
            messageHandler = function(messageId) {
              return _this.events.serially(function(go) {
                go(function() {
                  return _this.getMessage(_this.name, messageId);
                });
                go(function(message) {
                  if (message == null) {
                    return null;
                  }
                  if (message.requestId == null) {
                    return message;
                  }
                  return _this.events.source(function(events) {
                    return _this.events.serially(function(go) {
                      go(function() {
                        return _this.getMessage(message.from, message.requestId);
                      });
                      return go(function(request) {
                        return _this.events.serially(function(go) {
                          go(function() {
                            if (request != null) {
                              return _this.deleteMessage(message.from, message.requestId);
                            } else {
                              return _this.deleteMessage(_this.name, messageId);
                            }
                          });
                          return go(function() {
                            return events.emit("success", (request != null ? message : null));
                          });
                        })();
                      });
                    })();
                  });
                });
                go(function(message) {
                  var _message;
                  if (message != null) {
                    _message = {
                      content: message.content
                    };
                    _message.from = message.requestId != null ? message.to : message.from;
                    _message.to = message.requestId != null ? message.from : message.to;
                    _message.requestId = message.requestId != null ? message.requestId : message.id;
                    if (message.requestId != null) {
                      _message.responseId = message.id;
                    }
                    return _this.fire({
                      event: "message",
                      content: _message
                    });
                  }
                });
                return go(function() {
                  var ref, ref1;
                  if (((ref = _this.channels["message"]) != null ? (ref1 = ref.handlers) != null ? ref1.length : void 0 : void 0) > 0) {
                    return _this.queue.once("message", messageHandler);
                  }
                });
              })();
            };
            if (_this.superOn == null) {
              _this.superOn = _this.on;
            }
            _this.on = function(event, handler) {
              _this.superOn(event, handler);
              if (event === "message") {
                return _this.queue.once("message", messageHandler);
              }
            };
            return events.emit("success");
          });
        };
      })(this));
    };
    DurableChannel.prototype.end = function() {
      this.ending = true;
      clearTimeout(this.timeoutMonitor);
      return this.transport.end(this.name + ".queue", !this.isTransportShared);
    };
    return DurableChannel;
  })(EventChannel);
  module.exports = DurableChannel;
}).call(this);