Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Passing a channel as an argument valid? #2

Closed
loganpowell opened this issue Aug 9, 2019 · 2 comments
Closed

Passing a channel as an argument valid? #2

loganpowell opened this issue Aug 9, 2019 · 2 comments

Comments

@loganpowell
Copy link

Hey man! Just now getting a chance to play with this. Wondering if I'm just doing something wrong, but the way I usually use channels are as arguments to an async function. Is something like this possible?

const namer = (_I_, _O_) =>
  co(function *() {
    const name = yield _I_.take() //?
    const fancy_name = `${name}izer`
    yield _O_.put(fancy_name)
  })

const _I_ = new Channel()
const _O_ = new Channel()

co(function *() {
  yield _I_.put("Clojure") //?
  yield namer(_I_, _O_)
  const fancy_name = yield _O_.take()
  console.log("hello", fancy_name)
})
@nicolasdao
Copy link
Owner

nicolasdao commented Aug 10, 2019

Hi @loganpowell ,

You have a few deadlocks here

Deadlock 1

Description

yield _I_.put("Clojure") waits until a first brick is taken from the _I_ chan, but this will only happen when your namer function is called (yield _I_.take()). Because the namer waits until yield _I_.put("Clojure") yields a result, you are in a deadlock.

Solution

One way of fixing that issue (though not idiomatic of how CSP is supposed to be used) is to not wait for a process to take a brick from the _I_ channel:

co(function *() {
    _I_.put("Clojure")
    yield namer(_I_, _O_)
    const fancy_name = yield _O_.take()
    console.log("hello", fancy_name)
})

However, even with this, you'll run into the second deadlock.

Deadlock 2

Description

The namer function waits for a brick to be taken from the _O_ channel (yield _O_.put(fancy_name)). The only line of code that unlocks that wait is occurring after the namer invocation. That's the second deadlock.

Solution

One way of fixing that issue (though not idiomatic of how CSP is supposed to be used) is to not wait for taking a brick from the _O_ channel in the namer function. The new namer function would look like this:

const namer = (_I_, _O_) => co(function *() {
    const name = yield _I_.take() // This waits until a first brick is added on the _I_ channel.
    const fancy_name = `${name}izer`
    _O_.put(fancy_name)
})

Combined Solutions

If we put those 2 solutions together, the code looks like this:

const co = require('co')
const { Channel } = require("core-async")

const namer = (_I_, _O_) => co(function *() {
    const name = yield _I_.take() // This waits until a first brick is added on the _I_ channel.
    const fancy_name = `${name}izer`
    _O_.put(fancy_name) // REMOVING DEADLOCK 2
})

const _I_ = new Channel()
const _O_ = new Channel()

co(function *() {
    _I_.put("Clojure") // REMOVING DEADLOCK 1
    yield namer(_I_, _O_)
    const fancy_name = yield _O_.take()
    console.log("hello", fancy_name)
})

Better Approach

However, as I briefly mentioned before, this is not idiomatic of CSP. Indeed, what if the namer function is supposed to do more work after _O_.put(fancy_name) and that work must happen after there is a confirmation that another process has taken that brick from the _O_ channel. We could make the same remark for the _I_.put("Clojure"). What if we need to add a piece of work between _I_.put("Clojure") and yield namer(_I_, _O_) that can only be executed when there is confirmation that the "Clojure" brick has been taken from the _I_ channel. This solution would have to be refactored.

Your original namer function is fine. It works properly and does not contain deadlocks, but the way it is being used leads to deadlocks. So here is a suggestion of how your code could be re-written in what I think, IMHO, might be a more idiomatic CSP style.

const co = require('co')
const { Channel } = require("core-async")

// This is your original func
const namer = (_I_, _O_) => co(function *() {
    const name = yield _I_.take() // This waits until a first brick is added on the _I_ channel.
    const fancy_name = `${name}izer`
    yield _O_.put(fancy_name) // This waits until a first request for a brick is emitted on the _O_ channel.
})

const _I_ = new Channel()
const _O_ = new Channel()

// 'namer' does stuff asynchronously
co(function *() {
    console.log(`Doing some work before 'namer' starts`)
    yield namer(_I_, _O_)
    console.log(`Doing some more work now that 'namer' is done`)
})

// Asynchronously do things each time a brick is added to the _O_ channel.
co(function *() {
    const fancy_name = yield _O_.take()
    console.log("hello", fancy_name)
})

// Asynchronously do things each time a brick is taken from the _I_ channel.
co(function *() {
    yield _I_.put("Clojure") //?
    console.log(`Great, somebody took the 'Clojure' brick. I can do other awesome stuff now.`)
})

If you're craving for more Clojure stuff in JS (e.g., combining CSP patterns with transmuters), then a more mature library is js-csp. The only reason I built core-async instead of using js-csp is that I needed the alts function.

I hope my explanation shed some light on your problem.

Cheers,

Nic

@loganpowell
Copy link
Author

Dude, just awesome all around.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants