@@ -2,10 +2,14 @@ package concurrent
2
2
import scala .collection .mutable , mutable .ListBuffer
3
3
import scala .util .boundary .Label
4
4
import runtime .suspend
5
- import Async .{Listener , await }
5
+ import Async .{Listener , await , Yes }
6
+
7
+ /** An unbounded channel
8
+ * Unbounded channels are composable async sources.
9
+ */
10
+ class UnboundedChannel [T ] extends Async .Source [T ]:
11
+ type CanFilter = Yes
6
12
7
- /** An unbounded channel */
8
- class UnboundedChannel [T ] extends Async .ComposableSource [T ]:
9
13
private val pending = ListBuffer [T ]()
10
14
private val waiting = mutable.Set [Listener [T ]]()
11
15
@@ -45,69 +49,108 @@ class UnboundedChannel[T] extends Async.ComposableSource[T]:
45
49
46
50
end UnboundedChannel
47
51
52
+ /** An unbuffered, synchronous channel. Senders and readers both block
53
+ * until a communication between them happens.
54
+ * The channel provides two async sources, one for reading and one for
55
+ * sending. The two sources are not composable. This allows a simple
56
+ * implementation strategy where at each point either some senders
57
+ * are waiting for matching readers, or some readers are waiting for matching
58
+ * senders, or the channel is idle, i.e. there are no waiting readers or senders.
59
+ * If a send operation encounters some waiting readers, or a read operation
60
+ * encounters some waiting sender the data is transmitted directly. Otherwise
61
+ * we add the operation to the corresponding waiting pending set.
62
+ */
48
63
trait SyncChannel [T ]:
49
- def canRead : Async .Source [T ]
50
- def canSend : Async .Source [Listener [T ]]
64
+ thisCannel =>
65
+
66
+ type CanFilter
67
+
68
+ val canRead : Async .Source [T ] { type CanFilter = thisCannel.CanFilter }
69
+ val canSend : Async .Source [Listener [T ]] { type CanFilter = thisCannel.CanFilter }
51
70
52
71
def send (x : T )(using Async ): Unit = await(canSend)(x)
53
72
54
73
def read ()(using Async ): T = await(canRead)
55
74
56
75
object SyncChannel :
57
- def apply [T ](): SyncChannel [T ] = new Impl [T ]:
58
- val canRead = new ReadSource
59
- val canSend = new SendSource
76
+ def apply [T ](): SyncChannel [T ] = Impl [T ]()
77
+
78
+ class Impl [ T ] extends SyncChannel [ T ] :
60
79
61
- abstract class Impl [T ] extends SyncChannel [T ]:
62
- protected val pendingReads = mutable.Set [Listener [T ]]()
63
- protected val pendingSends = mutable.Set [Listener [Listener [T ]]]()
80
+ private val pendingReads = mutable.Set [Listener [T ]]()
81
+ private val pendingSends = mutable.Set [Listener [Listener [T ]]]()
64
82
65
83
protected def link [T ](pending : mutable.Set [T ], op : T => Boolean ): Boolean =
66
84
pending.headOption match
67
- case Some (elem) => op(elem); true
85
+ case Some (elem) =>
86
+ val ok = op(elem)
87
+ if ! ok then
88
+ // Since sources are not filterable, we can be here only if a race
89
+ // was lost and the entry was not yet removed. In that case, remove
90
+ // it here.
91
+ pending -= pending.head
92
+ link(pending, op)
93
+ ok
68
94
case None => false
69
95
70
96
private def collapse [T ](k2 : Listener [Listener [T ]]): Option [T ] =
71
97
var r : Option [T ] = None
72
98
if k2 { x => r = Some (x); true } then r else None
73
99
74
- protected class ReadSource extends Async .Source [T ]:
100
+ private class ReadSource extends Async .Source [T ]:
101
+ type CanFilter = Impl .this .CanFilter
75
102
def poll (k : Listener [T ]): Boolean =
76
103
link(pendingSends, sender => collapse(sender).map(k) == Some (true ))
77
104
def onComplete (k : Listener [T ]): Unit =
78
105
if ! poll(k) then pendingReads += k
79
106
def dropListener (k : Listener [T ]): Unit =
80
107
pendingReads -= k
81
108
82
- protected class SendSource extends Async .Source [Listener [T ]]:
109
+ private class SendSource extends Async .Source [Listener [T ]]:
110
+ type CanFilter = Impl .this .CanFilter
83
111
def poll (k : Listener [Listener [T ]]): Boolean =
84
112
link(pendingReads, k(_))
85
113
def onComplete (k : Listener [Listener [T ]]): Unit =
86
114
if ! poll(k) then pendingSends += k
87
115
def dropListener (k : Listener [Listener [T ]]): Unit =
88
116
pendingSends -= k
89
- end Impl
90
117
118
+ val canRead = new ReadSource
119
+ val canSend = new SendSource
120
+ end Impl
91
121
end SyncChannel
92
122
93
- trait ComposableSyncChannel [T ] extends SyncChannel [T ]:
94
- def canRead : Async .ComposableSource [T ]
95
- def canSend : Async .ComposableSource [Listener [T ]]
96
-
97
- object ComposableSyncChannel :
98
- def apply [T ](): ComposableSyncChannel [T ] = new Impl [T ]:
99
- val canRead = new ComposableReadSource
100
- val canSend = new ComposableSendSource
101
-
102
- abstract class Impl [T ] extends SyncChannel .Impl [T ], ComposableSyncChannel [T ]:
123
+ object FilterableSyncChannel :
124
+ def apply [T ](): SyncChannel [T ] { type CanFilter = Yes } = Impl [T ]()
103
125
126
+ class Impl [T ] extends SyncChannel .Impl [T ]:
127
+ type CanFilter = Yes
104
128
override protected def link [T ](pending : mutable.Set [T ], op : T => Boolean ): Boolean =
129
+ // Since sources are filterable, we have to match all pending readers or writers
130
+ // against the incoming request
105
131
pending.iterator.find(op) match
106
132
case Some (elem) => pending -= elem; true
107
133
case None => false
108
134
109
- class ComposableReadSource extends ReadSource , Async .ComposableSource [T ]
110
- class ComposableSendSource extends SendSource , Async .ComposableSource [Listener [T ]]
111
- end Impl
112
-
113
- end ComposableSyncChannel
135
+ end FilterableSyncChannel
136
+
137
+ def TestRace =
138
+ val c1, c2 = FilterableSyncChannel [Int ]()
139
+ val s = c1.canSend
140
+ val c3 = Async .race(c1.canRead, c2.canRead)
141
+ val c4 = c3.filter(_ >= 0 )
142
+ val d0 = SyncChannel [Int ]()
143
+ val d1 = Async .race(c1.canRead, c2.canRead, d0.canRead)
144
+ val d2 = d1.map(_ + 1 )
145
+ val c5 = Async .either(c1.canRead, c2.canRead)
146
+ .map:
147
+ case Left (x) => - x
148
+ case Right (x) => x
149
+ .filter(_ >= 0 )
150
+
151
+ // val d3bad = d1.filter(_ >= 0)
152
+ val d5 = Async .either(c1.canRead, d2)
153
+ .map:
154
+ case Left (x) => - x
155
+ case Right (x) => x
156
+ // val d6bad = d5.filter(_ >= 0)
0 commit comments