lossy-chan
This package provides fast, bounded, and lossy broadcast channels backed by a ring buffer.
As long as receivers keep up with senders, a lossy-chan acts as a regular channel. For example:
(i, o) <- LC.new 64 -- buffer size
for_ [0..1000] $ \m -> do
LC.send i m
m' <- LC.receive o
assertM (m' == m)
If a receiver lags behind, it loses access to the oldest messages first. For example:
(i, o) <- LC.new 64
for_ [0..64] (LC.send i) -- sends 65 messages
m' <- LC.receive o
assertM (m' == 1) -- 0 was dropped
A lossy-chan channel is split into an input and an output channel, both of which are thread-safe.
If you use a single output channel in two threads, the messages are split between the two.
If you want two output channls that both receive every message, you can duplicate the output channel.
The new output channel receives messages starting where the duplicated channel left off. For example:
(i, o1) <- LC.new 64
for_ [1,2] (LC.send i)
m1 <- LC.receive o1
assertM (m1 == 1) -- receive the 1 on o1
o2 <- LC.dup o1 -- duplicate o1
m2 <- LC.receive o2 -- receive the 2 on o2
assertM (m2 == 2)
m2' <- LC.receive o1 -- receive the 2 on o1
assertM (m2' == 2)
The lossy-chan buffer uses access counts to ensure that messages can be garbage collected as soon as they've been received by every output channel. This relies on finalizers and is not guaranteed to be prompt. For example:
(i, o) <- LC.new 64
LC.send i bigThing
void (LC.receive o) -- bigThing can now be garbage collected
If the input channel is garbage collected, output channels can continue to receive values until they reach the end of the buffer, at which point receive will throw a NoInChanError. Alternatively, the GHC RTS may also throw a BlockedIndefinitelyOnSTM exception. For example:
(i, o) <- LC.new 64
LC.send i 1
m1 <- LC.receive o
assertM (m1 == 1) -- receive the 1
m2 <- LC.receive o -- throws NoInChanError or BlockedIndefinitelyOnSTM
Benchmarks
This section shows the results of time and space benchmarks that compare lossy-chan with various other channels implementations:
- The
base benchmark uses Control.Concurrent.Chan.
- The
stm benchmark uses Control.Concurrent.STM.TChan.
- The
unagi-chan benchmark uses Control.Concurrent.Chan.Unagi.
- The
lossy-chan-N benchmarks use Control.Concurrent.LossyChan with buffer size N.
The benchmark itself was adapted from the unagi-chan's benchmark, and consists of some number of sender threads, each of which sends 10,000 messages, and some number of receiver threads, each of which tries to receive 10,000 messages.
Since lossy-chan channels may lose some messages, receiver threads terminate when they catch a NoInChanError or BlockedIndefinitelyOnSTM exception.
This isn't a fair comparison, since none of the other channel implementations is lossy. However, it should give you some idea of the performance and memory usage characteristics of lossy-chan.
Time
The time benchmark, created using criterion, shows that a lossy-chan channel is roughly as fast as an unagi-chan channel, and both are significantly faster than the base and stm channels. Moreover, the buffer size doesn't appear to significanly affect the performance of a lossy-chan channel.

Space
The space benchmark, created using weigh, shows that a lossy-chan channel has much lower maximum residency when compared to the other channel implementations, and that its maximum residency scales with the buffer size. This is expected from the use of a ring buffer.
| Benchmark |
Case |
Allocated |
Max Residency |
base |
1 senders, 1 receivers |
25,604,448 |
8,348,840 |
|
2 senders, 2 receivers |
51,208,176 |
12,275,048 |
|
3 senders, 3 receivers |
76,811,928 |
16,699,824 |
|
4 senders, 4 receivers |
96,154,064 |
24,617,800 |
stm |
1 senders, 1 receivers |
16,805,400 |
8,830,408 |
|
2 senders, 2 receivers |
33,609,720 |
9,845,000 |
|
3 senders, 3 receivers |
50,414,848 |
14,717,096 |
|
4 senders, 4 receivers |
55,414,888 |
17,660,312 |
unagi-chan |
1 senders, 1 receivers |
18,439,984 |
4,540,792 |
|
2 senders, 2 receivers |
36,862,432 |
4,541,464 |
|
3 senders, 3 receivers |
55,277,096 |
10,573,080 |
|
4 senders, 4 receivers |
73,698,768 |
10,573,984 |
lossy-chan-2 |
1 senders, 1 receivers |
12,008,624 |
3,088 |
|
2 senders, 2 receivers |
24,013,384 |
4,960 |
|
3 senders, 3 receivers |
36,018,184 |
6,816 |
|
4 senders, 4 receivers |
48,021,576 |
8,672 |
lossy-chan-4 |
1 senders, 1 receivers |
12,008,928 |
3,168 |
|
2 senders, 2 receivers |
24,014,392 |
5,040 |
|
3 senders, 3 receivers |
36,018,456 |
6,896 |
|
4 senders, 4 receivers |
48,021,664 |
8,752 |
lossy-chan-8 |
1 senders, 1 receivers |
12,009,536 |
3,328 |
|
2 senders, 2 receivers |
24,015,448 |
5,200 |
|
3 senders, 3 receivers |
36,019,056 |
7,056 |
|
4 senders, 4 receivers |
48,024,952 |
8,912 |
lossy-chan-16 |
1 senders, 1 receivers |
12,010,472 |
3,648 |
|
2 senders, 2 receivers |
24,017,080 |
5,520 |
|
3 senders, 3 receivers |
36,019,840 |
7,376 |
|
4 senders, 4 receivers |
48,028,224 |
9,232 |
lossy-chan-32 |
1 senders, 1 receivers |
12,011,728 |
4,288 |
|
2 senders, 2 receivers |
24,017,720 |
6,160 |
|
3 senders, 3 receivers |
36,028,936 |
8,016 |
|
4 senders, 4 receivers |
48,037,104 |
9,872 |
lossy-chan-64 |
1 senders, 1 receivers |
12,014,464 |
5,568 |
|
2 senders, 2 receivers |
24,030,232 |
7,440 |
|
3 senders, 3 receivers |
36,041,456 |
9,296 |
|
4 senders, 4 receivers |
48,067,576 |
11,152 |
lossy-chan-128 |
1 senders, 1 receivers |
12,021,448 |
8,128 |
|
2 senders, 2 receivers |
24,047,128 |
10,000 |
|
3 senders, 3 receivers |
36,065,520 |
11,856 |
|
4 senders, 4 receivers |
48,083,512 |
13,712 |