Commit 2c8063f4 authored by Kirill Smelkov's avatar Kirill Smelkov

*: Channels must be compared by ==, not by "is" even for nilchan

In a followup commit we are going to add channel types (e.g. chan of
double, chan of int, etc) and accordingly there will be several nil
channel objects, e.g. nil(dtype=int), nil(dtype=double) etc, which will
be separate python objects. Even without data types, another planned
change is to add directional channels, e.g. a channel instance that can
only send, but not receive and vice versa(*).

This way for the same underlying channel object, there can be several
pychan objects that point to it - even for nil channel - e.g. nilchan
and `niltx = nilchan.txonly()` that creates another pychan object
pointing to the same underlying nil.

Since we want all channels (of the same type) that point to the same
underlying channel to compare as same, we cannot use "is" for comparison
and have to use ==. In other words channels, both at C and Python level,
should be perceived as pointers, of which there can be multiple ones
pointing to the same location, and thus == has to be used to compare
them.

(*) see https://golang.org/ref/spec#Channel_types for details.
parent f6fab7b5
......@@ -60,7 +60,7 @@ Goroutines and channels
exchange data in between either threads or coroutines via channels. `chan`
creates a new channel with Go semantic - either synchronous or buffered. Use
`chan.recv`, `chan.send` and `chan.close` for communication. `nilchan`
stands for the nil channel. `select` can be used to multiplex on several
stands for nil channel. `select` can be used to multiplex on several
channels. For example::
ch1 = chan() # synchronous channel
......@@ -186,7 +186,7 @@ Cython/nogil API follows:
`go` spawns new task - a coroutine, or thread, depending on activated runtime.
`chan[T]` represents a channel with Go semantic and elements of type `T`.
Use `makechan[T]` to create new channel, and `chan[T].recv`, `chan[T].send`,
`chan[T].close` for communication. `nil` stands for the nil channel. `select`
`chan[T].close` for communication. `nil` stands for nil channel. `select`
can be used to multiplex on several channels. For example::
cdef nogil:
......
......@@ -154,6 +154,9 @@ cdef class pychan:
# (if it was present also somewhere else - draining would be incorrect)
if pych._ch == NULL:
return
# TODO: in the future there could be multiple pychan[object] wrapping
# the same underlying raw channel: e.g. ch=chan(); ch2=ch.txonly()
# -> drain underlying channel only when its last reference goes to 0.
cdef int refcnt = _chanrefcnt(pych._ch)
if refcnt != 1:
# cannot raise py-level exception in __dealloc__
......@@ -230,6 +233,17 @@ cdef class pychan:
else:
return super(pychan, pych).__repr__()
# pychan == pychan
def __hash__(pychan pych):
return <Py_hash_t>pych._ch
def __eq__(pychan a, object rhs):
if not isinstance(rhs, pychan):
return False
cdef pychan b = rhs
if a._ch != b._ch:
return False
return True
# pynilchan is the nil py channel.
#
......
......@@ -233,7 +233,7 @@ class _BaseCtx(object):
# if parent can never be canceled (e.g. it is background) - we
# don't need to propagate cancel from it.
pdone = parent.done()
if pdone is nilchan:
if pdone == nilchan:
continue
# parent is cancellable - glue to propagate cancel from it to us
......
......@@ -43,22 +43,22 @@ bg = context.background()
# deadlines are tested in test_deadline.
def test_context():
assert bg.err() is None
assert bg.done() is nilchan
assert bg.done() == nilchan
assert bg.deadline() is None
assert not ready(bg.done())
assert bg.value("hello") is None
ctx1, cancel1 = context.with_cancel(bg)
assert ctx1.done() is not bg.done()
assert ctx1.done() != bg.done()
assertCtx(ctx1, Z)
ctx11, cancel11 = context.with_cancel(ctx1)
assert ctx11.done() is not ctx1.done()
assert ctx11.done() != ctx1.done()
assertCtx(ctx1, {ctx11})
assertCtx(ctx11, Z)
ctx111 = context.with_value(ctx11, "hello", "alpha")
assert ctx111.done() is ctx11.done()
assert ctx111.done() is ctx11.done() # == and same pyobject
assert ctx111.value("hello") == "alpha"
assert ctx111.value("abc") is None
assertCtx(ctx1, {ctx11})
......@@ -66,7 +66,7 @@ def test_context():
assertCtx(ctx111, Z)
ctx1111 = context.with_value(ctx111, "beta", "gamma")
assert ctx1111.done() is ctx11.done()
assert ctx1111.done() is ctx11.done() # == and same pyobject
assert ctx1111.value("hello") == "alpha"
assert ctx1111.value("beta") == "gamma"
assert ctx1111.value("abc") is None
......@@ -77,7 +77,7 @@ def test_context():
ctx12 = context.with_value(ctx1, "hello", "world")
assert ctx12.done() is ctx1.done()
assert ctx12.done() is ctx1.done() # == and same pyobject
assert ctx12.value("hello") == "world"
assert ctx12.value("abc") is None
assertCtx(ctx1, {ctx11, ctx12})
......@@ -87,7 +87,7 @@ def test_context():
assertCtx(ctx12, Z)
ctx121, cancel121 = context.with_cancel(ctx12)
assert ctx121.done() is not ctx12.done()
assert ctx121.done() != ctx12.done()
assert ctx121.value("hello") == "world"
assert ctx121.value("abc") is None
assertCtx(ctx1, {ctx11, ctx12})
......@@ -98,7 +98,7 @@ def test_context():
assertCtx(ctx121, Z)
ctx1211 = context.with_value(ctx121, "мир", "май")
assert ctx1211.done() is ctx121.done()
assert ctx1211.done() is ctx121.done() # == and same pyobject
assert ctx1211.value("hello") == "world"
assert ctx1211.value("мир") == "май"
assert ctx1211.value("abc") is None
......@@ -111,8 +111,8 @@ def test_context():
assertCtx(ctx1211, Z)
ctxM, cancelM = context.merge(ctx1111, ctx1211)
assert ctxM.done() is not ctx1111.done()
assert ctxM.done() is not ctx1211.done()
assert ctxM.done() != ctx1111.done()
assert ctxM.done() != ctx1211.done()
assert ctxM.value("hello") == "alpha"
assert ctxM.value("мир") == "май"
assert ctxM.value("beta") == "gamma"
......@@ -158,30 +158,30 @@ def test_deadline():
d3 = t0 + 30*dt
ctx1, cancel1 = context.with_deadline(bg, d2)
assert ctx1.done() is not bg.done()
assert ctx1.done() != bg.done()
assertCtx(ctx1, Z, deadline=d2)
ctx11 = context.with_value(ctx1, "a", "b")
assert ctx11.done() is ctx1.done()
assert ctx11.done() is ctx1.done() # == and same pyobject
assert ctx11.value("a") == "b"
assertCtx(ctx1, {ctx11}, deadline=d2)
assertCtx(ctx11, Z, deadline=d2)
ctx111, cancel111 = context.with_cancel(ctx11)
assert ctx111.done() is not ctx11.done
assert ctx111.done() != ctx11.done
assertCtx(ctx1, {ctx11}, deadline=d2)
assertCtx(ctx11, {ctx111}, deadline=d2)
assertCtx(ctx111, Z, deadline=d2)
ctx1111, cancel1111 = context.with_deadline(ctx111, d3) # NOTE deadline > parent
assert ctx1111.done() is not ctx111.done()
assert ctx1111.done() != ctx111.done()
assertCtx(ctx1, {ctx11}, deadline=d2)
assertCtx(ctx11, {ctx111}, deadline=d2)
assertCtx(ctx111, {ctx1111}, deadline=d2)
assertCtx(ctx1111, Z, deadline=d2) # NOTE not d3
ctx12, cancel12 = context.with_deadline(ctx1, d1)
assert ctx12.done() is not ctx1.done()
assert ctx12.done() != ctx1.done()
assertCtx(ctx1, {ctx11, ctx12}, deadline=d2)
assertCtx(ctx11, {ctx111}, deadline=d2)
assertCtx(ctx111, {ctx1111}, deadline=d2)
......@@ -189,8 +189,8 @@ def test_deadline():
assertCtx(ctx12, Z, deadline=d1)
ctxM, cancelM = context.merge(ctx1111, ctx12)
assert ctxM.done() is not ctx1111.done()
assert ctxM.done() is not ctx12.done()
assert ctxM.done() != ctx1111.done()
assert ctxM.done() != ctx12.done()
assert ctxM.value("a") == "b"
assertCtx(ctx1, {ctx11, ctx12}, deadline=d2)
assertCtx(ctx11, {ctx111}, deadline=d2)
......@@ -221,7 +221,7 @@ def test_deadline():
# with_timeout
ctx, cancel = context.with_timeout(bg, 10*dt)
assert ctx.done() is not bg.done()
assert ctx.done() != bg.done()
d = ctx.deadline()
assert abs(d - (time.now() + 10*dt)) < 1*dt
assertCtx(ctx, Z, deadline=d)
......
......@@ -738,6 +738,27 @@ def _test_blockforever():
with panics("t: blocks forever"): select((z.send, 1), z.recv)
def test_chan_misc():
nilch = nilchan
assert nilch == nilch # nil == nil
# channels can be compared, different channels differ
assert nilch != None # just in case
ch1 = chan()
ch2 = chan()
ch3 = chan()
assert ch1 != ch2; assert ch1 == ch1
assert ch1 != ch3; assert ch2 == ch2
assert ch2 != ch3; assert ch3 == ch3
assert hash(nilch) != hash(ch1)
assert hash(nilch) != hash(ch2)
assert hash(nilch) != hash(ch3)
assert nilch != ch1
assert nilch != ch2
assert nilch != ch3
def test_func():
# test how @func(cls) works
# this also implicitly tests just @func, since @func(cls) uses that.
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment