<commit_before>var stack = [], push = [].push, JUMP = {}

function Cadence (parent, self, steps, vargs, callback) {
    this.parent = parent
    this.finalizers = []
    this.self = self
    this.steps = steps
    this.callback = callback
    this.loop = false
    this.cadence = parent == null ? this : parent.cadence
    this.cadences = []
    this.results = []
    this.errors = []
    this.called = 0
    this.index = 0
    this.sync = true
    this.waiting = false
    this.vargs = vargs
}

// TODO Expand on this. You keep coming back here and saying, oh, no, I need to
// give up on a step if there is an error. I'm not returning from this because
// there is an error and all the callbacks are not returning. I need to return
// immediately if there is an error, and then have a lot more code to deal with
// the stragglers that return.
//
// Go back to your code. Try to explain to me why one error first callback
// function returning an error is preventing another, completely different
// callback function from returning an error. Pretend the are calls to open two
// separate files and tell me why the orderly error reporting of the inability
// to open one file should prevent the completion of the opening of another.
// You're probably doing something complicated, a callback is deferred, and
// neglecting to notify the deferred callback of an error.
//
// In short, this code is fine. If you were not using Cadence either you would
// not have noticed the problem, or else you've have some sort of straggler
// issue causing your code to continue after you've responded to an error.
//
// TODO Update. Yes, good point. This is rare in production code, but I do
// encounter it a lot in testing where I'm testing race conditions in concurrent
// code, the kind of code that Cadence has made it easy for me to write.
//
// Because this is rare in production, it's not all that difficult to accept
// that Cadence should return on the first error, then silently swallow all
// subsequent errors. That seems ugly, but not advancing is also ugly, and in
// both cases the ugliness is avoided by writing code that runs serially.
// (Parallel code using the Node.js event loop is a boondoggle.)
//
// The logic isn't that much more difficult.
//
// It does present challenges when you consider what it means to run finalizers
// early.

Cadence.prototype.resolveCallback = function (result, error, vargs) {
    if (error == null) {
        result.vargs = vargs
    } else {
        this.errors.push(error)
    }
    if (++this.called === this.results.length) {
        if (this.waiting) {
            this.invoke()
        } else {
            this.sync = true
        }
    }
}

Cadence.prototype.createCallback = function () {
    var self = this
    var result = { vargs: [] }

    self.results.push(result)
    self.sync = false

    return callback

    function callback (error) {
        var I = arguments.length
        var vargs = new Array
        for (var i = 1; i < I; i++) {
            vargs[i - 1] = arguments[i]
        }
        self.resolveCallback(result, error, vargs)
    }
}

function async () {
    var cadence = stack[stack.length - 1]
    var I = arguments.length
    if (I) {
        var vargs = new Array(I)
        for (var i = 0; i < I; i++) {
            vargs[i] = arguments[i]
        }
        cadence.cadences.push(new Cadence(cadence, cadence.self, vargs, [], cadence.createCallback()))
    } else {
        return cadence.createCallback()
    }
}

async.continue = { jump: JUMP, index: 0, break: false }
async.break = { jump: JUMP, index: Infinity, break: true }

function call (fn, self, vargs) {
    try {
        var ret = fn.apply(self, vargs)
    } catch (e) {
        return [ ret, e ]
    }
    return [ ret ]
}

Cadence.prototype.invoke = function () {
    var vargs, fn
    for (;;) {
        if (this.errors.length) {
            // Break on error cadence is frustrated further by catch blocks that
            // would restore forward motion. I suppose you'd only short-circuit
            // cadences subordinate to this cadence.
            if (this.catcher) {
                var catcher = this.catcher, errors = this.errors
                fn = function () {
                    return catcher.call(this, errors[0], errors)
                }
            } else {
                fn = null
                this.loop = false
            }
        } else {
            if (this.results.length == 0) {
                // We had no async callbacks, so use the return value.
                vargs = this.vargs
                // Check for a loop controller in the return values.
                if (vargs[0] && vargs[0].jump === JUMP) {
                    var jump = vargs.shift()
                    // Walk up to the jumping cadence setting all the
                    // sub-cadences along the way to their last step. We
                    // continue with the current cadence, not the destination.
                    // We don't skip finalizers. When we continue, if the
                    // current cadence is not the jumping cadence, we're going
                    // to run the exit procedures for each sub-cadence.
                    var destination = jump.cadence || this.cadence
                    var iterator = this
                    while (destination !== iterator) {
                        iterator.loop = false
                        iterator.index = iterator.steps.length
                        iterator = iterator.parent
                    }
                    // Set the index and stop looping if this is a `break`.
                    iterator.index = Math.min(jump.index, iterator.steps.length)
                    iterator.loop = iterator.loop && ! jump.break
                }
            } else {
                // Combine the results of all the callbacks into an single array
                // of arguments that will be used to invoke the next step.
                this.vargs = vargs = this.results[0].vargs
                for (var i = 1, I = this.results.length; i < I; i++) {
                    var vargs_ = this.results[i].vargs
                    for (var j = 0, J = vargs_.length; j < J; j++) {
                        vargs.push(vargs_[j])
                    }
                }
            }
            // On to the next step.
            fn = this.steps[this.index++]
        }

        if (fn == null) {
            if (this.finalizers.length) {
                // We're going to continue to loop until all the finalizers have
                // executed. The step index is going to go beyond length of the
                // step array, but that's okay.
                var finalizer = this.finalizers.pop(), errors = this.errors
                fn = function () {
                    async(function () {
                        return finalizer.vargs
                    }, [finalizer.steps[0], function (error) {
                        if (errors.length) throw errors[0]
                        throw error
                    }], function () {
                        if (errors.length) throw errors[0]
                        return vargs
                    })
                }
            } else if (this.loop) {
                // Go back to the first step.
                fn = this.steps[0]
                this.index = 1
            } else if (this.errors.length) {
                // Return the first error we received.
                (this.callback).apply(null, [ this.errors[0] ])
                break
            } else {
                if (vargs.length !== 0) {
                    vargs.unshift(null)
                }
                (this.callback).apply(null, vargs)
                break
            }
        }

        this.called = 0
        this.cadences = []
        this.results = []
        this.errors = []
        this.sync = true
        this.waiting = false
        this.catcher = null

        if (Array.isArray(fn)) {
            if (fn.length === 1) {
                this.finalizers.push({ steps: fn, vargs: vargs })
                continue
            } else if (fn.length === 2) {
                this.catcher = fn[1]
                fn = fn[0]
            } else if (fn.length === 3) {
                var filter = fn
                this.catcher = function (error) {
                    if (filter[1].test(error.code || error.message)) {
                        return filter[2].call(this, error)
                    } else {
                        throw error
                    }
                }
                fn = fn[0]
            } else {
                this.vargs = [ vargs ]
                continue
            }
        }

        stack.push(this)

        var ret = call(fn, this.self, vargs)
               // ^^^^

        stack.pop()

        if (ret.length === 2) {
            this.errors.push(ret[1])
            this.vargs = vargs
            this.sync = true
        } else {
            for (var i = 0, I = this.cadences.length; i < I; i++) {
                this.cadences[i].invoke()
            }
            if (ret[0] !== void(0)) {
                this.vargs = Array.isArray(ret[0]) ? ret[0] : [ ret[0] ]
            }
        }

        if (!this.sync) {
            this.waiting = true
            break
        }
    }
}

function execute (self, steps, vargs, callback) {
    new Cadence(null, self, steps, vargs, callback).invoke()
}

function cadence () {
    var I = arguments.length
    var steps = new Array
    for (var i = 0; i < I; i++) {
        steps.push(arguments[i])
    }
    var f
    // Preserving arity costs next to nothing; the call to `execute` in
    // these functions will be inlined. The airty function itself will never
    // be inlined because it is in a different context than that of our
    // dear user, but it will be compiled.
    switch (steps[0].length) {
    case 0:
        f = function () {
            var I = arguments.length - 1
            var vargs = new Array(I + 1)
            vargs[0] = async
            for (var i = 0; i < I; i++) {
                vargs[i + 1] = arguments[i]
            }
            execute(this, steps, vargs, arguments[i])
        }
        break
    case 1:
        f = function (one) {
            var I = arguments.length - 1
            var vargs = new Array(I + 1)
            vargs[0] = async
            for (var i = 0; i < I; i++) {
                vargs[i + 1] = arguments[i]
            }
            execute(this, steps, vargs, arguments[i])
        }
        break
    case 2:
        f = function (one, two) {
            var I = arguments.length - 1
            var vargs = new Array(I + 1)
            vargs[0] = async
            for (var i = 0; i < I; i++) {
                vargs[i + 1] = arguments[i]
            }
            execute(this, steps, vargs, arguments[i])
        }
        break
    case 3:
        f = function (one, two, three) {
            var I = arguments.length - 1
            var vargs = new Array(I + 1)
            vargs[0] = async
            for (var i = 0; i < I; i++) {
                vargs[i + 1] = arguments[i]
            }
            execute(this, steps, vargs, arguments[i])
        }
        break
    case 4:
        f = function (one, two, three, four) {
            var I = arguments.length - 1
            var vargs = new Array(I + 1)
            vargs[0] = async
            for (var i = 0; i < I; i++) {
                vargs[i + 1] = arguments[i]
            }
            execute(this, steps, vargs, arguments[i])
        }
        break
    default:
        // Avert your eyes if you're squeamish.
        var args = []
        for (var i = 0, I = steps[0].length; i < I; i++) {
            args[i] = '_' + i
        }
        f = (new Function('execute', 'steps', 'async', '                    \n\
            return function (' + args.join(',') + ') {                      \n\
                var I = arguments.length - 1                                \n\
                var vargs = new Array(I + 1)                                \n\
                vargs[0] = async                                            \n\
                for (var i = 0; i < I; i++) {                               \n\
                    vargs[i + 1] = arguments[i]                             \n\
                }                                                           \n\
                execute(this, steps, vargs, arguments[i])                   \n\
            }                                                               \n\
       '))(execute, steps, async)
    }

    f.toString = function () { return steps[0].toString() }

    return f
}

function variadic (f, self) {
    return function () {
        var I = arguments.length
        var vargs = new Array
        for (var i = 0; i < I; i++) {
            vargs.push(arguments[i])
        }
        return f.call(self, vargs)
    }
}

async.loop = variadic(function (steps) {
    var cadence = stack[stack.length - 1]
    var vargs = steps.shift()
    var callback = cadence.createCallback()
    var looper = new Cadence(cadence, cadence.self, steps, [], callback)
    looper.loop = true
    looper.vargs = vargs
    looper.cadence = looper
    cadence.cadences.push(looper)
    return {
        continue: { jump: JUMP, index: 0, break: false, cadence: looper },
        break: { jump: JUMP, index: Infinity, break: true, cadence: looper }
    }
}, async)

async.forEach = variadic(function (steps) {
    var loop, vargs = steps.shift(), array = vargs.shift(), index = -1
    steps.unshift(vargs, variadic(function (vargs) {
        index++
        if (index === array.length) return [ loop.break ].concat(vargs)
        return [ array[index], index ].concat(vargs)
    }))
    return loop = this.loop.apply(this, steps)
}, async)

async.map = variadic(function (steps) {
    var loop, vargs = steps.shift(), array = vargs.shift(), index = -1, gather = []
    steps.unshift(vargs, variadic(function (vargs) {
        index++
        if (index === array.length) return [ loop.break, gather ]
        return [ array[index], index ].concat(vargs)
    }))
    steps.push(variadic(function (vargs) {
        gather.push.apply(gather, vargs)
    }))
    return loop = this.loop.apply(this, steps)
}, async)

module.exports = cadence
<commit_msg>Scrounging for optimizations, notes.<commit_after>var stack = [], push = [].push, JUMP = {}

function Cadence (parent, self, steps, vargs, callback) {
    this.parent = parent
    this.finalizers = []
    this.self = self
    this.steps = steps
    this.callback = callback
    this.loop = false
    this.cadence = parent == null ? this : parent.cadence
    this.cadences = []
    this.results = []
    this.errors = []
    this.called = 0
    this.index = 0
    this.sync = true
    this.waiting = false
    this.vargs = vargs
}

// TODO Expand on this. You keep coming back here and saying, oh, no, I need to
// give up on a step if there is an error. I'm not returning from this because
// there is an error and all the callbacks are not returning. I need to return
// immediately if there is an error, and then have a lot more code to deal with
// the stragglers that return.
//
// Go back to your code. Try to explain to me why one error first callback
// function returning an error is preventing another, completely different
// callback function from returning an error. Pretend the are calls to open two
// separate files and tell me why the orderly error reporting of the inability
// to open one file should prevent the completion of the opening of another.
// You're probably doing something complicated, a callback is deferred, and
// neglecting to notify the deferred callback of an error.
//
// In short, this code is fine. If you were not using Cadence either you would
// not have noticed the problem, or else you've have some sort of straggler
// issue causing your code to continue after you've responded to an error.
//
// TODO Update. Yes, good point. This is rare in production code, but I do
// encounter it a lot in testing where I'm testing race conditions in concurrent
// code, the kind of code that Cadence has made it easy for me to write.
//
// Because this is rare in production, it's not all that difficult to accept
// that Cadence should return on the first error, then silently swallow all
// subsequent errors. That seems ugly, but not advancing is also ugly, and in
// both cases the ugliness is avoided by writing code that runs serially.
// (Parallel code using the Node.js event loop is a boondoggle.)
//
// The logic isn't that much more difficult.
//
// It does present challenges when you consider what it means to run finalizers
// early.

Cadence.prototype.resolveCallback = function (result, error, vargs) {
    if (error == null) {
        result.vargs = vargs
    } else {
        this.errors.push(error)
    }
    if (++this.called === this.results.length) {
        if (this.waiting) {
            this.invoke()
        } else {
            this.sync = true
        }
    }
}

Cadence.prototype.createCallback = function () {
    var self = this
    var result = { vargs: [] }

    self.results.push(result)
    self.sync = false

    return callback

    function callback (error) {
        var I = arguments.length
        var vargs = new Array
        for (var i = 1; i < I; i++) {
            vargs[i - 1] = arguments[i]
        }
        self.resolveCallback(result, error, vargs)
    }
}

function async () {
    var cadence = stack[stack.length - 1]
    var I = arguments.length
    if (I) {
        var vargs = new Array(I)
        for (var i = 0; i < I; i++) {
            vargs[i] = arguments[i]
        }
        cadence.cadences.push(new Cadence(cadence, cadence.self, vargs, [], cadence.createCallback()))
    } else {
        return cadence.createCallback()
    }
}

async.continue = { jump: JUMP, index: 0, break: false }
async.break = { jump: JUMP, index: Infinity, break: true }

function call (fn, self, vargs) {
    try {
        var ret = fn.apply(self, vargs)
    } catch (e) {
        return [ ret, e ]
    }
    return [ ret ]
}

Cadence.prototype.invoke = function () {
    var vargs, fn
    for (;;) {
        if (this.errors.length) { // Critical path.
            // Break on error cadence is frustrated further by catch blocks that
            // would restore forward motion. I suppose you'd only short-circuit
            // cadences subordinate to this cadence.
            if (this.catcher) {
                var catcher = this.catcher, errors = this.errors
                fn = function () {
                    return catcher.call(this, errors[0], errors)
                }
            } else {
                fn = null
                this.loop = false
            }
        } else {
            if (this.results.length == 0) { // Critical path.
                // We had no async callbacks, so use the return value.
                vargs = this.vargs
                // Check for a loop controller in the return values.
                if (vargs[0] && vargs[0].jump === JUMP) {
                    var jump = vargs.shift()
                    // Walk up to the jumping cadence setting all the
                    // sub-cadences along the way to their last step. We
                    // continue with the current cadence, not the destination.
                    // We don't skip finalizers. When we continue, if the
                    // current cadence is not the jumping cadence, we're going
                    // to run the exit procedures for each sub-cadence.
                    var destination = jump.cadence || this.cadence
                    var iterator = this
                    while (destination !== iterator) {
                        iterator.loop = false
                        iterator.index = iterator.steps.length
                        iterator = iterator.parent
                    }
                    // Set the index and stop looping if this is a `break`.
                    iterator.index = Math.min(jump.index, iterator.steps.length)
                    iterator.loop = iterator.loop && ! jump.break
                }
            } else {
                // Combine the results of all the callbacks into an single array
                // of arguments that will be used to invoke the next step.
                this.vargs = vargs = this.results[0].vargs
                for (var i = 1, I = this.results.length; i < I; i++) {
                    var vargs_ = this.results[i].vargs
                    for (var j = 0, J = vargs_.length; j < J; j++) {
                        vargs.push(vargs_[j])
                    }
                }
            }
            // On to the next step.
            fn = this.steps[this.index++]
        }

        if (fn == null) { // Critical path.
            if (this.finalizers.length) {
                // We're going to continue to loop until all the finalizers have
                // executed. The step index is going to go beyond length of the
                // step array, but that's okay.
                var finalizer = this.finalizers.pop(), errors = this.errors
                fn = function () {
                    async(function () {
                        return finalizer.vargs
                    }, [finalizer.steps[0], function (error) {
                        if (errors.length) throw errors[0]
                        throw error
                    }], function () {
                        if (errors.length) throw errors[0]
                        return vargs
                    })
                }
            } else if (this.loop) {
                // Go back to the first step.
                fn = this.steps[0]
                this.index = 1
            } else if (this.errors.length) {
                // Return the first error we received.
                (this.callback).apply(null, [ this.errors[0] ])
                break
            } else {
                if (vargs.length !== 0) {
                    vargs.unshift(null)
                }
                (this.callback).apply(null, vargs)
                break
            }
        }

        this.called = 0
        this.cadences = []
        this.results = []
        this.errors = []
        this.sync = true
        this.waiting = false
        this.catcher = null

        if (Array.isArray(fn)) { // Critical path.
            if (fn.length === 1) {
                this.finalizers.push({ steps: fn, vargs: vargs })
                continue
            } else if (fn.length === 2) {
                this.catcher = fn[1]
                fn = fn[0]
            } else if (fn.length === 3) {
                var filter = fn
                this.catcher = function (error) {
                    if (filter[1].test(error.code || error.message)) {
                        return filter[2].call(this, error)
                    } else {
                        throw error
                    }
                }
                fn = fn[0]
            } else {
                this.vargs = [ vargs ]
                continue
            }
        }

        stack.push(this)

        var ret = call(fn, this.self, vargs)
               // ^^^^

        stack.pop()

        if (ret.length === 2) {
            this.errors.push(ret[1])
            this.vargs = vargs
            this.sync = true
        } else {
            // The only one that could be removed if we where to invoke cadences
            // directly and immediately when created. It would change loop
            // labeling so that the loop label was always passed in as a final
            // argument to the variadic arguments. This would in cause a gotcha
            // where the user needs to make sure that each loop gets the same
            // arguments, ah, and that's surprising because often times we're
            // not thinking about the return at the end.
            for (var i = 0, I = this.cadences.length; i < I; i++) {
                this.cadences[i].invoke()
            }
            if (ret[0] !== void(0)) {
                this.vargs = Array.isArray(ret[0]) ? ret[0] : [ ret[0] ]
            }
        }

        if (!this.sync) {
            this.waiting = true
            break
        }
    }
}

function execute (self, steps, vargs, callback) {
    new Cadence(null, self, steps, vargs, callback).invoke()
}

function cadence () {
    var I = arguments.length
    var steps = new Array
    for (var i = 0; i < I; i++) {
        steps.push(arguments[i])
    }
    var f
    // Preserving arity costs next to nothing; the call to `execute` in
    // these functions will be inlined. The airty function itself will never
    // be inlined because it is in a different context than that of our
    // dear user, but it will be compiled.
    switch (steps[0].length) {
    case 0:
        f = function () {
            var I = arguments.length - 1
            var vargs = new Array(I + 1)
            vargs[0] = async
            for (var i = 0; i < I; i++) {
                vargs[i + 1] = arguments[i]
            }
            execute(this, steps, vargs, arguments[i])
        }
        break
    case 1:
        f = function (one) {
            var I = arguments.length - 1
            var vargs = new Array(I + 1)
            vargs[0] = async
            for (var i = 0; i < I; i++) {
                vargs[i + 1] = arguments[i]
            }
            execute(this, steps, vargs, arguments[i])
        }
        break
    case 2:
        f = function (one, two) {
            var I = arguments.length - 1
            var vargs = new Array(I + 1)
            vargs[0] = async
            for (var i = 0; i < I; i++) {
                vargs[i + 1] = arguments[i]
            }
            execute(this, steps, vargs, arguments[i])
        }
        break
    case 3:
        f = function (one, two, three) {
            var I = arguments.length - 1
            var vargs = new Array(I + 1)
            vargs[0] = async
            for (var i = 0; i < I; i++) {
                vargs[i + 1] = arguments[i]
            }
            execute(this, steps, vargs, arguments[i])
        }
        break
    case 4:
        f = function (one, two, three, four) {
            var I = arguments.length - 1
            var vargs = new Array(I + 1)
            vargs[0] = async
            for (var i = 0; i < I; i++) {
                vargs[i + 1] = arguments[i]
            }
            execute(this, steps, vargs, arguments[i])
        }
        break
    default:
        // Avert your eyes if you're squeamish.
        var args = []
        for (var i = 0, I = steps[0].length; i < I; i++) {
            args[i] = '_' + i
        }
        f = (new Function('execute', 'steps', 'async', '                    \n\
            return function (' + args.join(',') + ') {                      \n\
                var I = arguments.length - 1                                \n\
                var vargs = new Array(I + 1)                                \n\
                vargs[0] = async                                            \n\
                for (var i = 0; i < I; i++) {                               \n\
                    vargs[i + 1] = arguments[i]                             \n\
                }                                                           \n\
                execute(this, steps, vargs, arguments[i])                   \n\
            }                                                               \n\
       '))(execute, steps, async)
    }

    f.toString = function () { return steps[0].toString() }

    return f
}

function variadic (f, self) {
    return function () {
        var I = arguments.length
        var vargs = new Array
        for (var i = 0; i < I; i++) {
            vargs.push(arguments[i])
        }
        return f.call(self, vargs)
    }
}

async.loop = variadic(function (steps) {
    var cadence = stack[stack.length - 1]
    var vargs = steps.shift()
    var callback = cadence.createCallback()
    var looper = new Cadence(cadence, cadence.self, steps, [], callback)
    looper.loop = true
    looper.vargs = vargs
    looper.cadence = looper
    cadence.cadences.push(looper)
    return {
        continue: { jump: JUMP, index: 0, break: false, cadence: looper },
        break: { jump: JUMP, index: Infinity, break: true, cadence: looper }
    }
}, async)

async.forEach = variadic(function (steps) {
    var loop, vargs = steps.shift(), array = vargs.shift(), index = -1
    steps.unshift(vargs, variadic(function (vargs) {
        index++
        if (index === array.length) return [ loop.break ].concat(vargs)
        return [ array[index], index ].concat(vargs)
    }))
    return loop = this.loop.apply(this, steps)
}, async)

async.map = variadic(function (steps) {
    var loop, vargs = steps.shift(), array = vargs.shift(), index = -1, gather = []
    steps.unshift(vargs, variadic(function (vargs) {
        index++
        if (index === array.length) return [ loop.break, gather ]
        return [ array[index], index ].concat(vargs)
    }))
    steps.push(variadic(function (vargs) {
        gather.push.apply(gather, vargs)
    }))
    return loop = this.loop.apply(this, steps)
}, async)

module.exports = cadence
<|endoftext|>


<commit_before>/*
 * Copyright (c) 2016-2018 Untu, Inc.
 * This code is licensed under Eclipse Public License - v 1.0.
 * The full license text can be found in LICENSE.txt file and
 * on the Eclipse official site (https://www.eclipse.org/legal/epl-v10.html).
 */

'use strict';

let Actor = require('./actor.js');
let net = require('net');
let http = require('http');
let P = require('bluebird');
let _ = require('underscore');

/**
 * An actor that is run in separate sub-process on local machine.
 */
class ForkedActor extends Actor {
  /**
   * @param {Object} options Actor options.
   * - {ActorSystem} system Actor system.
   * - {Actor} parent Parent actor.
   * - {Object} definition Actor behaviour definition.
   * - {Object} config Actor configuration.
   * - {Object} [bus] Message bus to send/receive messages.
   * - {Actor} actor Wrapped actor.
   */
  constructor(options) {
    super({
      system: options.system,
      parent: options.parent,
      definition: options.definition,
      id: options.actor.getId(),
      name: options.actor.getName(),
      config: options.config
    });

    this.system = options.system;
    this.bus = options.bus;
    this.actor = options.actor;
    this.idCounter = 1;
    this.responsePromises = {};
    this.timeouts = [];
    this.timeoutWorkerInterval = setInterval(() => {
      let now = _.now();

      while (this.timeouts.length > 0 && this.timeouts[0].timeout <= now) {
        let timeoutObject = this.timeouts.shift();
        let msgId = timeoutObject.msgId;
        let promise = this.responsePromises[msgId];
        delete this.responsePromises[msgId];

        promise && promise.reject(new Error('Response timed out.'));
      }
    }, 1000);
  }

  initialize() {
    return P.resolve()
      .then(() => this.actor.initialize(this))
      .then(() => {
        if (!this.bus) throw new Error('No bus specified for actor: ' + this);

        if (!this.listening) {
          this._setBus(this.bus);
        }

        this._setState('ready');
      });
  }

  getMode() {
    return 'forked';
  }

  send0(topic, ...message) {
    return this._sendActorMessage(topic, message, { receive: false });
  }

  sendAndReceive0(topic, ...message) {
    return this._sendActorMessage(topic, message, { receive: true });
  }

  metrics0() {
    return this.actor.metrics();
  }

  /**
   * Transmits bus message through this actor's system bus instance.
   *
   * @param {String} event Event name.
   * @param {String[]} senders Chain of actors (ID's) who sent that message.
   * @param {any} args Emission arguments.
   */
  transmitBusMessage(event, senders = [], ...args) {
    if (['destroyed', 'destroying', 'crashed'].includes(this.getState())) return;

    if (!this.hasParent()) {
      this.getBus().emitFromActor(event, senders.concat(this.getId()), ...args);
    }

    this._send0({ type: 'system-bus-message', event, senders: senders.concat(this.getId()), args });
  }

  toString() {
    let name = this.getName();

    if (name) {
      return 'ForkedActor(' + this.id + ', ' + name + ')';
    }
    else {
      return 'ForkedActor(' + this.getId() + ')';
    }
  }

  /**
   * @returns {Actor} Wrapped actor.
   * @protected
   */
  _getActor() {
    return this.actor;
  }

  /**
   * Sets message bus to this actor and listens to the messages from this bus.
   *
   * @param {Object} bus Message bus.
   * @protected
   */
  _setBus(bus) {
    this.bus = bus;
    this.listening = true;

    // Listen for message responses.
    this.bus.on('message', (msg, handle) => {
      this.lastReceiveTimestamp = _.now();
      let log = this.getLog();

      log.debug('Received message:', msg);

      if (!msg.id) return log.warn('Missing ID in actor message (ignoring):', msg);

      if (msg.type == 'actor-message') {
        if (!msg.body) return this._sendErrorResponse(msg.id, 'Missing message body');

        let topic = msg.body.topic;

        if (!topic) return this._sendErrorResponse(msg.id, 'Missing message topic');

        let actor = this.actor;

        if (msg.actorId != this.getId()) {
          let parent = this.getParent();

          // Check if we've got a message to a parent.
          if (parent && parent.getId() == msg.actorId) {
            actor = parent;
          }
          else {
            return this._sendErrorResponse(msg.id, 'Target actor ID doesn\'t match neither self nor parent');
          }
        }

        this._decodeMessageBody(msg.body, handle)
          .then(message => {
            if (msg.body.receive) {
              return actor.sendAndReceive.apply(actor, [topic].concat(message))
                .then(resp => this._send0({
                  type: 'actor-response',
                  id: msg.id,
                  body: { response: resp }
                }));
            }
            else {
              return actor.send.apply(actor, [topic].concat(message));
            }
          })
          .catch(err => this._sendErrorResponse(msg.id, err.message));

        return;
      }

      if (msg.actorId != this.getId()) return;

      if (msg.type == 'actor-tree') {
        this.tree()
          .then(tree => this._send0({
            type: 'actor-response',
            id: msg.id,
            body: { response: tree }
          }))
          .catch(err => this._sendErrorResponse(msg.id, err.message));
      }
      else if (msg.type == 'actor-metrics') {
        this.metrics()
          .then(metrics => this._send0({
            type: 'actor-response',
            id: msg.id,
            body: { response: metrics }
          }))
          .catch(err => this._sendErrorResponse(msg.id, err.message));
      }
      else if (msg.type == 'actor-response' || msg.type == 'parent-pong') {
        let respPromise = this.responsePromises[msg.id];

        if (respPromise) {
          delete this.responsePromises[msg.id];
        }

        if (!msg.id) return log.warn(`Ignoring "${msg.type}" message with absent ID.`);

        if (!respPromise) return log.warn(`No pending promise for "${msg.type}":`, msg);

        if (msg.body) {
          let body = msg.body;

          if (body.error) {
            respPromise.reject(new Error(body.error));
          }
          else {
            respPromise.resolve(body.response);
          }
        }
        else {
          respPromise.resolve();
        }
      }
      else if (msg.type == 'destroy-actor') {
        this.actor.destroy().then(() => {
          this._send0({ type: 'actor-destroyed', id: msg.id });
        });
      }
      else if (msg.type == 'actor-destroyed') {
        clearInterval(this.timeoutWorkerInterval);
        let respPromise0 = this.responsePromises[msg.id];

        if (respPromise0) {
          delete this.responsePromises[msg.id];

          respPromise0.resolve();
        }

        this._send0({ type: 'actor-destroyed-ack' });
      }
      else if (msg.type == 'actor-destroyed-ack') {
        this.bus.removeAllListeners('message');

        log.debug('Destroying forked system for ' + this);

        this.system.destroy().catch(err => {
          log.error('Failed to destroy forked system: ' + err);
        });
      }
      else if (msg.type == 'parent-ping') {
        this._send0({ type: 'parent-pong', id: msg.id });
      }
      else if (msg.type == 'system-bus-message') {
        if (this.hasParent() &&
          !msg.senders.includes(this.getParent().getId()) &&
          ['forked', 'remote'].includes(this.getParent().getMode())
        ) {
          // NOTE: If this actor's id is not in the msg.senders list then this message was sent by its child through
          // the parent-child communication bus. New submittion must be initiated on the parent to notify its parent
          // about the message it recieved.
          this.getParent().actor.transmitBusMessage(msg.event, msg.senders, ...msg.args);
        }
        else {
          this.getBus().emitFromActor(msg.event, msg.senders, ...msg.args);
        }
      }
      else {
        log.warn('Ignoring message of an unknown type:', msg);
      }
    });

    // Remove message listener once child process has exited or crashed.
    this.bus.once('exit', () => {
      this.bus.removeAllListeners('message');
    });
  }

  _setState(state) {
    super._setState(state);

    if (state == 'crashed') {
      // Reject all pending response promises.
      _.each(this.responsePromises, promise => {
        promise && promise.reject(new Error('Actor crashed.'));
      });

      this.responsePromises = {};
      this.timeouts = [];
    }
  }

  /**
   * Sends an actor message to a forked actor and returns a promise, which is resolved with message response.
   *
   * @param {String} topic Message topic.
   * @param {Array} message Message.
   * @param {Object} options Operation options.
   * - {Boolean} receive Receive flag.
   * - {String} [actorId] ID of a remote actor to send message to (either self or parent).
   * @returns {*} Promise that yields a message response promise, if a receive flag is on. A promise
   * yields undefined if a receive flag is off.
   * @protected
   */
  _sendActorMessage(topic, message, options) {
    let actorMessage = {
      type: 'actor-message',
      body: {
        topic: topic,
        message: message,
        receive: options.receive
      }
    };

    if (message.length == 1 && message[0] instanceof net.Server) {
      if (message[0] instanceof http.Server) {
        actorMessage.body.message = { handleType: 'http.Server' };

        return this._send0(actorMessage, _.extend({}, options, { socketHandle: message[0] }));
      }
      else if (message[0] instanceof net.Server) {
        actorMessage.body.message = { handleType: 'net.Server' };

        return this._send0(actorMessage, _.extend({}, options, { socketHandle: message[0] }));
      }
    }

    let marshalledTypes = [];

    return P
      .map(message || [], (subMessage, idx) => {
        let marshaller;

        if (subMessage instanceof Actor) {
          marshaller = this._getReferenceMarshaller();
        }
        else {
          marshaller = this.system.getMarshallerForMessage(subMessage);
        }

        if (marshaller) {
          marshalledTypes[idx] = marshaller.type;

          return marshaller.marshall(subMessage);
        }

        return subMessage;
      })
      .then(messageParts => {
        if (messageParts.length == 1) {
          messageParts = messageParts[0];
          marshalledTypes = marshalledTypes[0];
        }

        actorMessage.body.message = messageParts;
        actorMessage.body.marshalledType = marshalledTypes;

        return this._send0(actorMessage, options);
      });
  }

  /**
   * @returns {ForkedActorReferenceMarshaller} Reference marshaller.
   * @protected
   */
  _getReferenceMarshaller() {
    return this.system.getForkedActorReferenceMarshaller();
  }

  /**
   * Sends an error response.
   *
   * @param {String} msgId Message ID.
   * @param {String} errorText Error text.
   * @returns {*} Operation promise.
   * @private
   */
  _sendErrorResponse(msgId, errorText) {
    return this._send0({
      type: 'actor-response',
      id: msgId,
      body: {
        error: errorText
      }
    });
  }

  /**
   * Sends an arbitrary message to a forked actor.
   *
   * @param {Object} msg Message to send.
   * @param {Object} [options] Operation options.
   * - {Boolean} receive Receive flag.
   * - {String} [actorId] ID of a remote actor to send message to (either self or parent).
   * @returns {*} Promise that yields a message response promise, if a receive flag is on. A promise
   * yields undefined if a receive flag is off.
   * @protected
   */
  _send0(msg, options = {}) {
    return new P((resolve, reject) => {
      let msgId = msg.id;

      if (!msgId) {
        msg.id = msgId = this.idCounter++;
      }

      msg.actorId = options.actorId || this.getId();

      let ret;

      if (options.receive) {
        let pending = P.pending();

        this.responsePromises[msgId] = pending;

        // Await for message response.
        ret = pending.promise;

        // Set message timeout, if needed.
        if (options.timeout > 0) {
          let timeout = _.now() + options.timeout;
          let timeoutObject = { timeout: timeout, msgId: msgId };
          let idx = _.sortedIndex(this.timeouts, timeoutObject, 'timeout');

          this.timeouts.splice(idx, 0, timeoutObject);
        }
      }

      if (this.getLog().isDebug()) {
        this.getLog().debug('Sending message:', msg);
      }

      let cb = err => {
        if (err) {
          delete this.responsePromises[msgId];

          return reject(err);
        }

        if (ret) {
          ret.then(resolve).catch(reject);
        }
        else {
          resolve();
        }
      };

      if (options.socketHandle) {
        this.bus.send(msg, options.socketHandle, cb);
      }
      else {
        this.bus.send(msg, cb);
      }
    });
  }

  /**
   * Decodes message body, received by this actor.
   *
   * @param {Object} body Message body.
   * @param {Object} [handle] Socket handle object.
   * @returns {Promise} Promise of decoded message in variable argument format.
   * @private
   */
  _decodeMessageBody(body, handle) {
    if (handle) {
      switch (body.message.handleType) {
        case 'net.Server':
          return P.resolve([handle]);

        case 'http.Server': {
          // Wrap net.Server into http.Server.
          let ret = http.createServer();
          ret.listen(handle);

          return P.resolve([ret]);
        }

        default:
          return P.reject(new Error('Unknown handle type: ' + body.message.handleType));
      }
    }

    let message = body.message;
    let marshalledTypes = body.marshalledType;

    if (!message) {
      message = [];
      marshalledTypes = [];
    }

    if (!_.isArray(message)) {
      message = [message];
      marshalledTypes = [marshalledTypes];
    }

    return P.map(message, (subMessage, idx) => {
      let type = marshalledTypes[idx];

      if (type) {
        let marshaller;

        if (type == 'InterProcessReference') {
          marshaller = this.system.getForkedActorReferenceMarshaller();
        }
        else if (type == 'InterHostReference') {
          marshaller = this.system.getRemoteActorReferenceMarshaller();
        }
        else {
          marshaller = this.system.getMarshaller(type);
        }

        return marshaller.unmarshall(subMessage);
      }

      return subMessage;
    });
  }

  /**
   * Returns a timestamp of last receipt of a packet from child endpoint.
   *
   * @returns {Number|undefined} Last child packet timestamp or undefined, if no
   * packet was yet received.
   * @protected
   */
  _getLastReceiveTimestamp() {
    return this.lastReceiveTimestamp;
  }
}

module.exports = ForkedActor;<commit_msg>Fixed actor reference marshalling.<commit_after>/*
 * Copyright (c) 2016-2018 Untu, Inc.
 * This code is licensed under Eclipse Public License - v 1.0.
 * The full license text can be found in LICENSE.txt file and
 * on the Eclipse official site (https://www.eclipse.org/legal/epl-v10.html).
 */

'use strict';

let Actor = require('./actor.js');
let ClientActorProxy = require('./client-actor-proxy.js');
let net = require('net');
let http = require('http');
let P = require('bluebird');
let _ = require('underscore');

/**
 * An actor that is run in separate sub-process on local machine.
 */
class ForkedActor extends Actor {
  /**
   * @param {Object} options Actor options.
   * - {ActorSystem} system Actor system.
   * - {Actor} parent Parent actor.
   * - {Object} definition Actor behaviour definition.
   * - {Object} config Actor configuration.
   * - {Object} [bus] Message bus to send/receive messages.
   * - {Actor} actor Wrapped actor.
   */
  constructor(options) {
    super({
      system: options.system,
      parent: options.parent,
      definition: options.definition,
      id: options.actor.getId(),
      name: options.actor.getName(),
      config: options.config
    });

    this.system = options.system;
    this.bus = options.bus;
    this.actor = options.actor;
    this.idCounter = 1;
    this.responsePromises = {};
    this.timeouts = [];
    this.timeoutWorkerInterval = setInterval(() => {
      let now = _.now();

      while (this.timeouts.length > 0 && this.timeouts[0].timeout <= now) {
        let timeoutObject = this.timeouts.shift();
        let msgId = timeoutObject.msgId;
        let promise = this.responsePromises[msgId];
        delete this.responsePromises[msgId];

        promise && promise.reject(new Error('Response timed out.'));
      }
    }, 1000);
  }

  initialize() {
    return P.resolve()
      .then(() => this.actor.initialize(this))
      .then(() => {
        if (!this.bus) throw new Error('No bus specified for actor: ' + this);

        if (!this.listening) {
          this._setBus(this.bus);
        }

        this._setState('ready');
      });
  }

  getMode() {
    return 'forked';
  }

  send0(topic, ...message) {
    return this._sendActorMessage(topic, message, { receive: false });
  }

  sendAndReceive0(topic, ...message) {
    return this._sendActorMessage(topic, message, { receive: true });
  }

  metrics0() {
    return this.actor.metrics();
  }

  /**
   * Transmits bus message through this actor's system bus instance.
   *
   * @param {String} event Event name.
   * @param {String[]} senders Chain of actors (ID's) who sent that message.
   * @param {any} args Emission arguments.
   */
  transmitBusMessage(event, senders = [], ...args) {
    if (['destroyed', 'destroying', 'crashed'].includes(this.getState())) return;

    if (!this.hasParent()) {
      this.getBus().emitFromActor(event, senders.concat(this.getId()), ...args);
    }

    this._send0({ type: 'system-bus-message', event, senders: senders.concat(this.getId()), args });
  }

  toString() {
    let name = this.getName();

    if (name) {
      return 'ForkedActor(' + this.id + ', ' + name + ')';
    }
    else {
      return 'ForkedActor(' + this.getId() + ')';
    }
  }

  /**
   * @returns {Actor} Wrapped actor.
   * @protected
   */
  _getActor() {
    return this.actor;
  }

  /**
   * Sets message bus to this actor and listens to the messages from this bus.
   *
   * @param {Object} bus Message bus.
   * @protected
   */
  _setBus(bus) {
    this.bus = bus;
    this.listening = true;

    // Listen for message responses.
    this.bus.on('message', (msg, handle) => {
      this.lastReceiveTimestamp = _.now();
      let log = this.getLog();

      log.debug('Received message:', msg);

      if (!msg.id) return log.warn('Missing ID in actor message (ignoring):', msg);

      if (msg.type == 'actor-message') {
        if (!msg.body) return this._sendErrorResponse(msg.id, 'Missing message body');

        let topic = msg.body.topic;

        if (!topic) return this._sendErrorResponse(msg.id, 'Missing message topic');

        let actor = this.actor;

        if (msg.actorId != this.getId()) {
          let parent = this.getParent();

          // Check if we've got a message to a parent.
          if (parent && parent.getId() == msg.actorId) {
            actor = parent;
          }
          else {
            return this._sendErrorResponse(msg.id, 'Target actor ID doesn\'t match neither self nor parent');
          }
        }

        this._decodeMessageBody(msg.body, handle)
          .then(message => {
            if (msg.body.receive) {
              return actor.sendAndReceive.apply(actor, [topic].concat(message))
                .then(resp => this._send0({
                  type: 'actor-response',
                  id: msg.id,
                  body: { response: resp }
                }));
            }
            else {
              return actor.send.apply(actor, [topic].concat(message));
            }
          })
          .catch(err => this._sendErrorResponse(msg.id, err.message));

        return;
      }

      if (msg.actorId != this.getId()) return;

      if (msg.type == 'actor-tree') {
        this.tree()
          .then(tree => this._send0({
            type: 'actor-response',
            id: msg.id,
            body: { response: tree }
          }))
          .catch(err => this._sendErrorResponse(msg.id, err.message));
      }
      else if (msg.type == 'actor-metrics') {
        this.metrics()
          .then(metrics => this._send0({
            type: 'actor-response',
            id: msg.id,
            body: { response: metrics }
          }))
          .catch(err => this._sendErrorResponse(msg.id, err.message));
      }
      else if (msg.type == 'actor-response' || msg.type == 'parent-pong') {
        let respPromise = this.responsePromises[msg.id];

        if (respPromise) {
          delete this.responsePromises[msg.id];
        }

        if (!msg.id) return log.warn(`Ignoring "${msg.type}" message with absent ID.`);

        if (!respPromise) return log.warn(`No pending promise for "${msg.type}":`, msg);

        if (msg.body) {
          let body = msg.body;

          if (body.error) {
            respPromise.reject(new Error(body.error));
          }
          else {
            respPromise.resolve(body.response);
          }
        }
        else {
          respPromise.resolve();
        }
      }
      else if (msg.type == 'destroy-actor') {
        this.actor.destroy().then(() => {
          this._send0({ type: 'actor-destroyed', id: msg.id });
        });
      }
      else if (msg.type == 'actor-destroyed') {
        clearInterval(this.timeoutWorkerInterval);
        let respPromise0 = this.responsePromises[msg.id];

        if (respPromise0) {
          delete this.responsePromises[msg.id];

          respPromise0.resolve();
        }

        this._send0({ type: 'actor-destroyed-ack' });
      }
      else if (msg.type == 'actor-destroyed-ack') {
        this.bus.removeAllListeners('message');

        log.debug('Destroying forked system for ' + this);

        this.system.destroy().catch(err => {
          log.error('Failed to destroy forked system: ' + err);
        });
      }
      else if (msg.type == 'parent-ping') {
        this._send0({ type: 'parent-pong', id: msg.id });
      }
      else if (msg.type == 'system-bus-message') {
        if (this.hasParent() &&
          !msg.senders.includes(this.getParent().getId()) &&
          ['forked', 'remote'].includes(this.getParent().getMode())
        ) {
          // NOTE: If this actor's id is not in the msg.senders list then this message was sent by its child through
          // the parent-child communication bus. New submittion must be initiated on the parent to notify its parent
          // about the message it recieved.
          this.getParent().actor.transmitBusMessage(msg.event, msg.senders, ...msg.args);
        }
        else {
          this.getBus().emitFromActor(msg.event, msg.senders, ...msg.args);
        }
      }
      else {
        log.warn('Ignoring message of an unknown type:', msg);
      }
    });

    // Remove message listener once child process has exited or crashed.
    this.bus.once('exit', () => {
      this.bus.removeAllListeners('message');
    });
  }

  _setState(state) {
    super._setState(state);

    if (state == 'crashed') {
      // Reject all pending response promises.
      _.each(this.responsePromises, promise => {
        promise && promise.reject(new Error('Actor crashed.'));
      });

      this.responsePromises = {};
      this.timeouts = [];
    }
  }

  /**
   * Sends an actor message to a forked actor and returns a promise, which is resolved with message response.
   *
   * @param {String} topic Message topic.
   * @param {Array} message Message.
   * @param {Object} options Operation options.
   * - {Boolean} receive Receive flag.
   * - {String} [actorId] ID of a remote actor to send message to (either self or parent).
   * @returns {*} Promise that yields a message response promise, if a receive flag is on. A promise
   * yields undefined if a receive flag is off.
   * @protected
   */
  _sendActorMessage(topic, message, options) {
    let actorMessage = {
      type: 'actor-message',
      body: {
        topic: topic,
        message: message,
        receive: options.receive
      }
    };

    if (message.length == 1 && message[0] instanceof net.Server) {
      if (message[0] instanceof http.Server) {
        actorMessage.body.message = { handleType: 'http.Server' };

        return this._send0(actorMessage, _.extend({}, options, { socketHandle: message[0] }));
      }
      else if (message[0] instanceof net.Server) {
        actorMessage.body.message = { handleType: 'net.Server' };

        return this._send0(actorMessage, _.extend({}, options, { socketHandle: message[0] }));
      }
    }

    let marshalledTypes = [];

    return P
      .map(message || [], (subMessage, idx) => {
        let marshaller;

        if (subMessage instanceof ClientActorProxy) {
          subMessage = subMessage.getWrapped();
        }

        if (subMessage instanceof Actor) {
          marshaller = this._getReferenceMarshaller();
        }
        else {
          marshaller = this.system.getMarshallerForMessage(subMessage);
        }

        if (marshaller) {
          marshalledTypes[idx] = marshaller.type;

          return marshaller.marshall(subMessage);
        }

        return subMessage;
      })
      .then(messageParts => {
        if (messageParts.length == 1) {
          messageParts = messageParts[0];
          marshalledTypes = marshalledTypes[0];
        }

        actorMessage.body.message = messageParts;
        actorMessage.body.marshalledType = marshalledTypes;

        return this._send0(actorMessage, options);
      });
  }

  /**
   * @returns {ForkedActorReferenceMarshaller} Reference marshaller.
   * @protected
   */
  _getReferenceMarshaller() {
    return this.system.getForkedActorReferenceMarshaller();
  }

  /**
   * Sends an error response.
   *
   * @param {String} msgId Message ID.
   * @param {String} errorText Error text.
   * @returns {*} Operation promise.
   * @private
   */
  _sendErrorResponse(msgId, errorText) {
    return this._send0({
      type: 'actor-response',
      id: msgId,
      body: {
        error: errorText
      }
    });
  }

  /**
   * Sends an arbitrary message to a forked actor.
   *
   * @param {Object} msg Message to send.
   * @param {Object} [options] Operation options.
   * - {Boolean} receive Receive flag.
   * - {String} [actorId] ID of a remote actor to send message to (either self or parent).
   * @returns {*} Promise that yields a message response promise, if a receive flag is on. A promise
   * yields undefined if a receive flag is off.
   * @protected
   */
  _send0(msg, options = {}) {
    return new P((resolve, reject) => {
      let msgId = msg.id;

      if (!msgId) {
        msg.id = msgId = this.idCounter++;
      }

      msg.actorId = options.actorId || this.getId();

      let ret;

      if (options.receive) {
        let pending = P.pending();

        this.responsePromises[msgId] = pending;

        // Await for message response.
        ret = pending.promise;

        // Set message timeout, if needed.
        if (options.timeout > 0) {
          let timeout = _.now() + options.timeout;
          let timeoutObject = { timeout: timeout, msgId: msgId };
          let idx = _.sortedIndex(this.timeouts, timeoutObject, 'timeout');

          this.timeouts.splice(idx, 0, timeoutObject);
        }
      }

      if (this.getLog().isDebug()) {
        this.getLog().debug('Sending message:', msg);
      }

      let cb = err => {
        if (err) {
          delete this.responsePromises[msgId];

          return reject(err);
        }

        if (ret) {
          ret.then(resolve).catch(reject);
        }
        else {
          resolve();
        }
      };

      if (options.socketHandle) {
        this.bus.send(msg, options.socketHandle, cb);
      }
      else {
        this.bus.send(msg, cb);
      }
    });
  }

  /**
   * Decodes message body, received by this actor.
   *
   * @param {Object} body Message body.
   * @param {Object} [handle] Socket handle object.
   * @returns {Promise} Promise of decoded message in variable argument format.
   * @private
   */
  _decodeMessageBody(body, handle) {
    if (handle) {
      switch (body.message.handleType) {
        case 'net.Server':
          return P.resolve([handle]);

        case 'http.Server': {
          // Wrap net.Server into http.Server.
          let ret = http.createServer();
          ret.listen(handle);

          return P.resolve([ret]);
        }

        default:
          return P.reject(new Error('Unknown handle type: ' + body.message.handleType));
      }
    }

    let message = body.message;
    let marshalledTypes = body.marshalledType;

    if (!message) {
      message = [];
      marshalledTypes = [];
    }

    if (!_.isArray(message)) {
      message = [message];
      marshalledTypes = [marshalledTypes];
    }

    return P.map(message, (subMessage, idx) => {
      let type = marshalledTypes[idx];

      if (type) {
        let marshaller;

        if (type == 'InterProcessReference') {
          marshaller = this.system.getForkedActorReferenceMarshaller();
        }
        else if (type == 'InterHostReference') {
          marshaller = this.system.getRemoteActorReferenceMarshaller();
        }
        else {
          marshaller = this.system.getMarshaller(type);
        }

        return marshaller.unmarshall(subMessage);
      }

      return subMessage;
    });
  }

  /**
   * Returns a timestamp of last receipt of a packet from child endpoint.
   *
   * @returns {Number|undefined} Last child packet timestamp or undefined, if no
   * packet was yet received.
   * @protected
   */
  _getLastReceiveTimestamp() {
    return this.lastReceiveTimestamp;
  }
}

module.exports = ForkedActor;<|endoftext|>


<commit_before>// 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.

couchTests.rewrite = function(debug) {
  // this test _rewrite handler

  var db = new CouchDB("test_suite_db", {"X-Couch-Full-Commit":"false"});
  db.deleteDb();
  db.createDb();

  if (debug) debugger;
  run_on_modified_server(
    [{section: "httpd",
      key: "authentication_handlers",
      value: "{couch_httpd_auth, special_test_authentication_handler}"},
     {section:"httpd",
      key: "WWW-Authenticate",
      value: "X-Couch-Test-Auth"}],

      function(){
        var designDoc = {
          _id:"_design/test",
          language: "javascript",
           _attachments:{
              "foo.txt": {
                content_type:"text/plain",
                data: "VGhpcyBpcyBhIGJhc2U2NCBlbmNvZGVkIHRleHQ="
              }
            },
          rewrites: [
            {
              "from": "foo",
              "to": "foo.txt"
            },
            {
              "from": "foo2",
              "to": "foo.txt",
              "method": "GET"
            },
            {
              "from": "hello/:id",
              "to": "_update/hello/:id",
              "method": "PUT"
            },
            {
              "from": "/welcome",
              "to": "_show/welcome"
            },
            {
              "from": "/welcome/:name",
              "to": "_show/welcome",
              "query": {
                "name": ":name"
              }
            },
            {
              "from": "/welcome2",
              "to": "_show/welcome",
              "query": {
                "name": "user"
              }
            },
            {
              "from": "/welcome3/:name",
              "to": "_update/welcome2/:name",
              "method": "PUT"
            },
            {
              "from": "/welcome3/:name",
              "to": "_show/welcome2/:name",
              "method": "GET"
            },
            {
             "from": "/welcome4/*",
             "to" : "_show/welcome3",
             "query": {
               "name": "*"
             }
            },
            {
             "from": "/welcome5/*",
             "to" : "_show/*",
             "query": {
               "name": "*"
             }
            },
            {
              "from": "simpleForm/basicView",
              "to": "_list/simpleForm/basicView",
            },
            {
              "from": "simpleForm/basicViewFixed",
              "to": "_list/simpleForm/basicView",
              "query": {
                "startkey": 3,
                "endkey": 8
              }
            },
            {
              "from": "simpleForm/basicViewPath/:start/:end",
              "to": "_list/simpleForm/basicView",
              "query": {
                "startkey": ":start",
                "endkey": ":end"
              }
            },
            {
              "from": "simpleForm/complexView",
              "to": "_list/simpleForm/complexView",
              "query": {
                "key": [1, 2]
              }
            },
            {
              "from": "simpleForm/complexView2",
              "to": "_list/simpleForm/complexView",
              "query": {
                "key": ["test", {}]
              }
            },
            {
              "from": "simpleForm/complexView3",
              "to": "_list/simpleForm/complexView",
              "query": {
                "key": ["test", ["test", "essai"]]
              }
            },
            {
              "from": "simpleForm/complexView4",
              "to": "_list/simpleForm/complexView2",
              "query": {
                "key": {"c": 1}
              }
            },
            {
              "from": "simpleForm/complexView5/:a/:b",
              "to": "_list/simpleForm/complexView3",
              "query": {
                "key": [":a", ":b"]
              }
            },
            {
              "from": "simpleForm/complexView6",
              "to": "_list/simpleForm/complexView3",
              "query": {
                "key": [":a", ":b"]
              }
            }
          ],
          lists: {
            simpleForm: stringFun(function(head, req) {
              log("simpleForm");
              send('
');
              var row, row_number = 0, prevKey, firstKey = null;
              while (row = getRow()) {
                row_number += 1;
                if (!firstKey) firstKey = row.key;
                prevKey = row.key;
                send('\n
Key: '+row.key
                +' Value: '+row.value
                +' LineNo: '+row_number+'
');
              }
              return '

FirstKey: '+ firstKey + ' LastKey: '+ prevKey+'
';
            }),
          },
          shows: {
            "welcome": stringFun(function(doc,req) {
              return "Welcome " + req.query["name"];
            }),
            "welcome2": stringFun(function(doc, req) {
              return "Welcome " + doc.name;
            }),
            "welcome3": stringFun(function(doc,req) {
              return "Welcome " + req.query["name"];
            })
          },
          updates: {
            "hello" : stringFun(function(doc, req) {
              if (!doc) {
                if (req.id) {
                  return [{
                    _id : req.id
                  }, "New World"]
                }
                return [null, "Empty World"];
              }
              doc.world = "hello";
              doc.edited_by = req.userCtx;
              return [doc, "hello doc"];
            }),
            "welcome2": stringFun(function(doc, req) {
              if (!doc) {
                if (req.id) {
                  return [{
                    _id: req.id,
                    name: req.id
                  }, "New World"]
                }
                return [null, "Empty World"];
              }
              return [doc, "hello doc"];
            })
          },
          views : {
            basicView : {
              map : stringFun(function(doc) {
                if (doc.integer) {
                  emit(doc.integer, doc.string);
                }

              })
            },
            complexView: {
              map: stringFun(function(doc) {
                if (doc.type == "complex") {
                  emit([doc.a, doc.b], doc.string);
                }
              })
            },
            complexView2: {
              map: stringFun(function(doc) {
                if (doc.type == "complex") {
                  emit(doc.a, doc.string);
                }
              })
            },
            complexView3: {
              map: stringFun(function(doc) {
                if (doc.type == "complex") {
                  emit(doc.b, doc.string);
                }
              })
            }
          }
        }

        db.save(designDoc);

        var docs = makeDocs(0, 10);
        db.bulkSave(docs);

        var docs2 = [
          {"a": 1, "b": 1, "string": "doc 1", "type": "complex"},
          {"a": 1, "b": 2, "string": "doc 2", "type": "complex"},
          {"a": "test", "b": {}, "string": "doc 3", "type": "complex"},
          {"a": "test", "b": ["test", "essai"], "string": "doc 4", "type": "complex"},
          {"a": {"c": 1}, "b": "", "string": "doc 5", "type": "complex"}
        ];

        db.bulkSave(docs2);

        // test simple rewriting

        req = CouchDB.request("GET", "/test_suite_db/_design/test/_rewrite/foo");
        T(req.responseText == "This is a base64 encoded text");
        T(req.getResponseHeader("Content-Type") == "text/plain");

        req = CouchDB.request("GET", "/test_suite_db/_design/test/_rewrite/foo2");
        T(req.responseText == "This is a base64 encoded text");
        T(req.getResponseHeader("Content-Type") == "text/plain");

        // test POST
        // hello update world

        var doc = {"word":"plankton", "name":"Rusty"}
        var resp = db.save(doc);
        T(resp.ok);
        var docid = resp.id;

        xhr = CouchDB.request("PUT", "/test_suite_db/_design/test/_rewrite/hello/"+docid);
        T(xhr.status == 201);
        T(xhr.responseText == "hello doc");
        T(/charset=utf-8/.test(xhr.getResponseHeader("Content-Type")))

        doc = db.open(docid);
        T(doc.world == "hello");

        req = CouchDB.request("GET", "/test_suite_db/_design/test/_rewrite/welcome?name=user");
        T(req.responseText == "Welcome user");

        req = CouchDB.request("GET", "/test_suite_db/_design/test/_rewrite/welcome/user");
        T(req.responseText == "Welcome user");

        req = CouchDB.request("GET", "/test_suite_db/_design/test/_rewrite/welcome2");
        T(req.responseText == "Welcome user");

        xhr = CouchDB.request("PUT", "/test_suite_db/_design/test/_rewrite/welcome3/test");
        T(xhr.status == 201);
        T(xhr.responseText == "New World");
        T(/charset=utf-8/.test(xhr.getResponseHeader("Content-Type")));

        xhr = CouchDB.request("GET", "/test_suite_db/_design/test/_rewrite/welcome3/test");
        T(xhr.responseText == "Welcome test");

        req = CouchDB.request("GET", "/test_suite_db/_design/test/_rewrite/welcome4/user");
        T(req.responseText == "Welcome user");

        req = CouchDB.request("GET", "/test_suite_db/_design/test/_rewrite/welcome5/welcome3");
        T(req.responseText == "Welcome welcome3");

        // get with query params
        xhr = CouchDB.request("GET", "/test_suite_db/_design/test/_rewrite/simpleForm/basicView?startkey=3&endkey=8");
        T(xhr.status == 200, "with query params");
        T(!(/Key: 1/.test(xhr.responseText)));
        T(/FirstKey: 3/.test(xhr.responseText));
        T(/LastKey: 8/.test(xhr.responseText));

        xhr = CouchDB.request("GET", "/test_suite_db/_design/test/_rewrite/simpleForm/basicViewFixed");
        T(xhr.status == 200, "with query params");
        T(!(/Key: 1/.test(xhr.responseText)));
        T(/FirstKey: 3/.test(xhr.responseText));
        T(/LastKey: 8/.test(xhr.responseText));

        // get with query params
        xhr = CouchDB.request("GET", "/test_suite_db/_design/test/_rewrite/simpleForm/basicViewFixed?startkey=4");
        T(xhr.status == 200, "with query params");
        T(!(/Key: 1/.test(xhr.responseText)));
        T(/FirstKey: 3/.test(xhr.responseText));
        T(/LastKey: 8/.test(xhr.responseText));

        // get with query params
        xhr = CouchDB.request("GET", "/test_suite_db/_design/test/_rewrite/simpleForm/basicViewPath/3/8");
        T(xhr.status == 200, "with query params");
        T(!(/Key: 1/.test(xhr.responseText)));
        T(/FirstKey: 3/.test(xhr.responseText));
        T(/LastKey: 8/.test(xhr.responseText));

        // get with query params
        xhr = CouchDB.request("GET", "/test_suite_db/_design/test/_rewrite/simpleForm/complexView");
        T(xhr.status == 200, "with query params");
        T(/FirstKey: [1, 2]/.test(xhr.responseText));

        xhr = CouchDB.request("GET", "/test_suite_db/_design/test/_rewrite/simpleForm/complexView2");
        T(xhr.status == 200, "with query params");
        T(/Value: doc 3/.test(xhr.responseText));

        xhr = CouchDB.request("GET", "/test_suite_db/_design/test/_rewrite/simpleForm/complexView3");
        T(xhr.status == 200, "with query params");
        T(/Value: doc 4/.test(xhr.responseText));

        xhr = CouchDB.request("GET", "/test_suite_db/_design/test/_rewrite/simpleForm/complexView4");
        T(xhr.status == 200, "with query params");
        T(/Value: doc 5/.test(xhr.responseText));

        xhr = CouchDB.request("GET", "/test_suite_db/_design/test/_rewrite/simpleForm/complexView5/test/essai");
        T(xhr.status == 200, "with query params");
        T(/Value: doc 4/.test(xhr.responseText));

        xhr = CouchDB.request("GET", "/test_suite_db/_design/test/_rewrite/simpleForm/complexView6?a=test&b=essai");
        T(xhr.status == 200, "with query params");
        T(/Value: doc 4/.test(xhr.responseText));

        // test path relative to server
        designDoc.rewrites.push({
           "from": "uuids",
           "to": "../../../_uuids"
        });
        T(db.save(designDoc).ok);

        var xhr = CouchDB.request("GET", "/test_suite_db/_design/test/_rewrite/uuids");
        T(xhr.status == 500);
        var result = JSON.parse(xhr.responseText);
        T(result.error == "insecure_rewrite_rule");

        run_on_modified_server(
          [{section: "httpd",
            key: "secure_rewrites",
            value: "false"}],
            function() {
              var xhr = CouchDB.request("GET", "/test_suite_db/_design/test/_rewrite/uuids?cache=bust");
              T(xhr.status == 200);
              var result = JSON.parse(xhr.responseText);
              T(result.uuids.length == 1);
              var first = result.uuids[0];
        });

  });

}
<commit_msg>add a test to check if rewrite works on views following a report on irc<commit_after>// 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.

couchTests.rewrite = function(debug) {
  // this test _rewrite handler

  var db = new CouchDB("test_suite_db", {"X-Couch-Full-Commit":"false"});
  db.deleteDb();
  db.createDb();

  if (debug) debugger;
  run_on_modified_server(
    [{section: "httpd",
      key: "authentication_handlers",
      value: "{couch_httpd_auth, special_test_authentication_handler}"},
     {section:"httpd",
      key: "WWW-Authenticate",
      value: "X-Couch-Test-Auth"}],

      function(){
        var designDoc = {
          _id:"_design/test",
          language: "javascript",
           _attachments:{
              "foo.txt": {
                content_type:"text/plain",
                data: "VGhpcyBpcyBhIGJhc2U2NCBlbmNvZGVkIHRleHQ="
              }
            },
          rewrites: [
            {
              "from": "foo",
              "to": "foo.txt"
            },
            {
              "from": "foo2",
              "to": "foo.txt",
              "method": "GET"
            },
            {
              "from": "hello/:id",
              "to": "_update/hello/:id",
              "method": "PUT"
            },
            {
              "from": "/welcome",
              "to": "_show/welcome"
            },
            {
              "from": "/welcome/:name",
              "to": "_show/welcome",
              "query": {
                "name": ":name"
              }
            },
            {
              "from": "/welcome2",
              "to": "_show/welcome",
              "query": {
                "name": "user"
              }
            },
            {
              "from": "/welcome3/:name",
              "to": "_update/welcome2/:name",
              "method": "PUT"
            },
            {
              "from": "/welcome3/:name",
              "to": "_show/welcome2/:name",
              "method": "GET"
            },
            {
             "from": "/welcome4/*",
             "to" : "_show/welcome3",
             "query": {
               "name": "*"
             }
            },
            {
             "from": "/welcome5/*",
             "to" : "_show/*",
             "query": {
               "name": "*"
             }
            },
            {
              "from": "basicView",
              "to": "_view/basicView",
            },
            {
              "from": "simpleForm/basicView",
              "to": "_list/simpleForm/basicView",
            },
            {
              "from": "simpleForm/basicViewFixed",
              "to": "_list/simpleForm/basicView",
              "query": {
                "startkey": 3,
                "endkey": 8
              }
            },
            {
              "from": "simpleForm/basicViewPath/:start/:end",
              "to": "_list/simpleForm/basicView",
              "query": {
                "startkey": ":start",
                "endkey": ":end"
              }
            },
            {
              "from": "simpleForm/complexView",
              "to": "_list/simpleForm/complexView",
              "query": {
                "key": [1, 2]
              }
            },
            {
              "from": "simpleForm/complexView2",
              "to": "_list/simpleForm/complexView",
              "query": {
                "key": ["test", {}]
              }
            },
            {
              "from": "simpleForm/complexView3",
              "to": "_list/simpleForm/complexView",
              "query": {
                "key": ["test", ["test", "essai"]]
              }
            },
            {
              "from": "simpleForm/complexView4",
              "to": "_list/simpleForm/complexView2",
              "query": {
                "key": {"c": 1}
              }
            },
            {
              "from": "simpleForm/complexView5/:a/:b",
              "to": "_list/simpleForm/complexView3",
              "query": {
                "key": [":a", ":b"]
              }
            },
            {
              "from": "simpleForm/complexView6",
              "to": "_list/simpleForm/complexView3",
              "query": {
                "key": [":a", ":b"]
              }
            },
            {
              "from": "/",
              "to": "_view/basicView",
            }
          ],
          lists: {
            simpleForm: stringFun(function(head, req) {
              log("simpleForm");
              send('
');
              var row, row_number = 0, prevKey, firstKey = null;
              while (row = getRow()) {
                row_number += 1;
                if (!firstKey) firstKey = row.key;
                prevKey = row.key;
                send('\n
Key: '+row.key
                +' Value: '+row.value
                +' LineNo: '+row_number+'
');
              }
              return '

FirstKey: '+ firstKey + ' LastKey: '+ prevKey+'
';
            }),
          },
          shows: {
            "welcome": stringFun(function(doc,req) {
              return "Welcome " + req.query["name"];
            }),
            "welcome2": stringFun(function(doc, req) {
              return "Welcome " + doc.name;
            }),
            "welcome3": stringFun(function(doc,req) {
              return "Welcome " + req.query["name"];
            })
          },
          updates: {
            "hello" : stringFun(function(doc, req) {
              if (!doc) {
                if (req.id) {
                  return [{
                    _id : req.id
                  }, "New World"]
                }
                return [null, "Empty World"];
              }
              doc.world = "hello";
              doc.edited_by = req.userCtx;
              return [doc, "hello doc"];
            }),
            "welcome2": stringFun(function(doc, req) {
              if (!doc) {
                if (req.id) {
                  return [{
                    _id: req.id,
                    name: req.id
                  }, "New World"]
                }
                return [null, "Empty World"];
              }
              return [doc, "hello doc"];
            })
          },
          views : {
            basicView : {
              map : stringFun(function(doc) {
                if (doc.integer) {
                  emit(doc.integer, doc.string);
                }

              })
            },
            complexView: {
              map: stringFun(function(doc) {
                if (doc.type == "complex") {
                  emit([doc.a, doc.b], doc.string);
                }
              })
            },
            complexView2: {
              map: stringFun(function(doc) {
                if (doc.type == "complex") {
                  emit(doc.a, doc.string);
                }
              })
            },
            complexView3: {
              map: stringFun(function(doc) {
                if (doc.type == "complex") {
                  emit(doc.b, doc.string);
                }
              })
            }
          }
        }

        db.save(designDoc);

        var docs = makeDocs(0, 10);
        db.bulkSave(docs);

        var docs2 = [
          {"a": 1, "b": 1, "string": "doc 1", "type": "complex"},
          {"a": 1, "b": 2, "string": "doc 2", "type": "complex"},
          {"a": "test", "b": {}, "string": "doc 3", "type": "complex"},
          {"a": "test", "b": ["test", "essai"], "string": "doc 4", "type": "complex"},
          {"a": {"c": 1}, "b": "", "string": "doc 5", "type": "complex"}
        ];

        db.bulkSave(docs2);

        // test simple rewriting

        req = CouchDB.request("GET", "/test_suite_db/_design/test/_rewrite/foo");
        T(req.responseText == "This is a base64 encoded text");
        T(req.getResponseHeader("Content-Type") == "text/plain");

        req = CouchDB.request("GET", "/test_suite_db/_design/test/_rewrite/foo2");
        T(req.responseText == "This is a base64 encoded text");
        T(req.getResponseHeader("Content-Type") == "text/plain");

        // test POST
        // hello update world

        var doc = {"word":"plankton", "name":"Rusty"}
        var resp = db.save(doc);
        T(resp.ok);
        var docid = resp.id;

        xhr = CouchDB.request("PUT", "/test_suite_db/_design/test/_rewrite/hello/"+docid);
        T(xhr.status == 201);
        T(xhr.responseText == "hello doc");
        T(/charset=utf-8/.test(xhr.getResponseHeader("Content-Type")))

        doc = db.open(docid);
        T(doc.world == "hello");

        req = CouchDB.request("GET", "/test_suite_db/_design/test/_rewrite/welcome?name=user");
        T(req.responseText == "Welcome user");

        req = CouchDB.request("GET", "/test_suite_db/_design/test/_rewrite/welcome/user");
        T(req.responseText == "Welcome user");

        req = CouchDB.request("GET", "/test_suite_db/_design/test/_rewrite/welcome2");
        T(req.responseText == "Welcome user");

        xhr = CouchDB.request("PUT", "/test_suite_db/_design/test/_rewrite/welcome3/test");
        T(xhr.status == 201);
        T(xhr.responseText == "New World");
        T(/charset=utf-8/.test(xhr.getResponseHeader("Content-Type")));

        xhr = CouchDB.request("GET", "/test_suite_db/_design/test/_rewrite/welcome3/test");
        T(xhr.responseText == "Welcome test");

        req = CouchDB.request("GET", "/test_suite_db/_design/test/_rewrite/welcome4/user");
        T(req.responseText == "Welcome user");

        req = CouchDB.request("GET", "/test_suite_db/_design/test/_rewrite/welcome5/welcome3");
        T(req.responseText == "Welcome welcome3");

        xhr = CouchDB.request("GET", "/test_suite_db/_design/test/_rewrite/basicView");
        T(xhr.status == 200, "view call");
        T(/{"total_rows":9/.test(xhr.responseText));

        xhr = CouchDB.request("GET", "/test_suite_db/_design/test/_rewrite/");
        T(xhr.status == 200, "view call");
        T(/{"total_rows":9/.test(xhr.responseText));

        // get with query params
        xhr = CouchDB.request("GET", "/test_suite_db/_design/test/_rewrite/simpleForm/basicView?startkey=3&endkey=8");
        T(xhr.status == 200, "with query params");
        T(!(/Key: 1/.test(xhr.responseText)));
        T(/FirstKey: 3/.test(xhr.responseText));
        T(/LastKey: 8/.test(xhr.responseText));

        xhr = CouchDB.request("GET", "/test_suite_db/_design/test/_rewrite/simpleForm/basicViewFixed");
        T(xhr.status == 200, "with query params");
        T(!(/Key: 1/.test(xhr.responseText)));
        T(/FirstKey: 3/.test(xhr.responseText));
        T(/LastKey: 8/.test(xhr.responseText));

        // get with query params
        xhr = CouchDB.request("GET", "/test_suite_db/_design/test/_rewrite/simpleForm/basicViewFixed?startkey=4");
        T(xhr.status == 200, "with query params");
        T(!(/Key: 1/.test(xhr.responseText)));
        T(/FirstKey: 3/.test(xhr.responseText));
        T(/LastKey: 8/.test(xhr.responseText));

        // get with query params
        xhr = CouchDB.request("GET", "/test_suite_db/_design/test/_rewrite/simpleForm/basicViewPath/3/8");
        T(xhr.status == 200, "with query params");
        T(!(/Key: 1/.test(xhr.responseText)));
        T(/FirstKey: 3/.test(xhr.responseText));
        T(/LastKey: 8/.test(xhr.responseText));

        // get with query params
        xhr = CouchDB.request("GET", "/test_suite_db/_design/test/_rewrite/simpleForm/complexView");
        T(xhr.status == 200, "with query params");
        T(/FirstKey: [1, 2]/.test(xhr.responseText));

        xhr = CouchDB.request("GET", "/test_suite_db/_design/test/_rewrite/simpleForm/complexView2");
        T(xhr.status == 200, "with query params");
        T(/Value: doc 3/.test(xhr.responseText));

        xhr = CouchDB.request("GET", "/test_suite_db/_design/test/_rewrite/simpleForm/complexView3");
        T(xhr.status == 200, "with query params");
        T(/Value: doc 4/.test(xhr.responseText));

        xhr = CouchDB.request("GET", "/test_suite_db/_design/test/_rewrite/simpleForm/complexView4");
        T(xhr.status == 200, "with query params");
        T(/Value: doc 5/.test(xhr.responseText));

        xhr = CouchDB.request("GET", "/test_suite_db/_design/test/_rewrite/simpleForm/complexView5/test/essai");
        T(xhr.status == 200, "with query params");
        T(/Value: doc 4/.test(xhr.responseText));

        xhr = CouchDB.request("GET", "/test_suite_db/_design/test/_rewrite/simpleForm/complexView6?a=test&b=essai");
        T(xhr.status == 200, "with query params");
        T(/Value: doc 4/.test(xhr.responseText));

        // test path relative to server
        designDoc.rewrites.push({
           "from": "uuids",
           "to": "../../../_uuids"
        });
        T(db.save(designDoc).ok);

        var xhr = CouchDB.request("GET", "/test_suite_db/_design/test/_rewrite/uuids");
        T(xhr.status == 500);
        var result = JSON.parse(xhr.responseText);
        T(result.error == "insecure_rewrite_rule");

        run_on_modified_server(
          [{section: "httpd",
            key: "secure_rewrites",
            value: "false"}],
            function() {
              var xhr = CouchDB.request("GET", "/test_suite_db/_design/test/_rewrite/uuids?cache=bust");
              T(xhr.status == 200);
              var result = JSON.parse(xhr.responseText);
              T(result.uuids.length == 1);
              var first = result.uuids[0];
        });

  });

}
<|endoftext|>