├── .travis.yml ├── COPYING ├── LICENSE-MIT-lua-channels ├── README.md ├── examples ├── sieve.lua ├── test1.lua └── test2.lua ├── lua-channels-0.1.0-0.rockspec └── lua-channels.lua /.travis.yml: -------------------------------------------------------------------------------- 1 | language: c 2 | 3 | env: 4 | - LUA="lua5.1" 5 | - LUA="lua5.2" 6 | - LUA="luajit" 7 | 8 | branches: 9 | only: 10 | - master 11 | 12 | install: 13 | - sudo apt-get -qq -y install luajit lua5.1 lua5.2 14 | - sudo ln -s /usr/bin/luajit* /usr/bin/luajit 15 | - find /usr/bin -name luajit\* 16 | 17 | script: "$LUA lua-channels.lua" 18 | 19 | notifications: 20 | email: 21 | recipients: 22 | - travis@popcount.org 23 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | Part of the code is derived from libtask library by Russ Cox which is 2 | distributed under the MIT license. 3 | 4 | This code is distributed under the MIT license, see the file 5 | LICENSE-MIT-lua-channels. 6 | -------------------------------------------------------------------------------- /LICENSE-MIT-lua-channels: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 Marek Majkowski 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/majek/lua-channels.png)](https://travis-ci.org/majek/lua-channels) 2 | 3 | Lua-Channels 4 | ============ 5 | 6 | *Go style Channels for Lua* 7 | 8 | This code is derived from libtask library by Russ Cox, mainly from 9 | channel.c. Semantically channels as implemented here are quite 10 | similar to channels from the Go language. 11 | 12 | Usage 13 | ----- 14 | 15 | This is an example of using an unbuffered channel: 16 | 17 | ``` 18 | local task = require('lua-channels') 19 | 20 | local function counter(channel) 21 | local i = 1 22 | while true do 23 | channel:send(i) 24 | i = i + 1 25 | end 26 | end 27 | 28 | local function main() 29 | local channel = task.Channel:new() 30 | task.spawn(counter, channel) 31 | assert(channel:recv() == 1) 32 | assert(channel:recv() == 2) 33 | assert(channel:recv() == 3) 34 | end 35 | 36 | task.spawn(main) 37 | task.scheduler() 38 | ``` 39 | 40 | lua-channels exposes: 41 | 42 | * task.spawn(fun, [...]) - run fun as a coroutine with given 43 | parameters. You should use this instead of 44 | coroutine.create() 45 | 46 | * task.scheduler() - can be run only from the main thread, executes 47 | all the stuff, resumes the coroutines that are 48 | blocked on channels that became available. You 49 | can only do non-blocking sends / receives from 50 | the main thread. 51 | 52 | * task.Channel:new([buffer size]) - create a new channel with given size 53 | 54 | * task.chanalt(alts, can_block) - run alt / select / multiplex over 55 | the alts structure. For example: 56 | 57 | * task.chanalt({{c = channel_1, op = task.RECV}, 58 | {c = channel_2, op = task.SEND, p = "hello"}}, true) 59 | 60 | This will block current coroutine until it's possible to receive 61 | from channel_1 or send to channel_2. chanalt returns a number of 62 | statement from alts that succeeded (1 or 2 here) and a received 63 | value if executed statement was RECV. 64 | 65 | Finally, if two alt statements can be fulfilled at the same time, 66 | we use math.random() to decide which one should go first. So it 67 | makes sense to initialize seed with something random. If you don't 68 | have access to an entropy source you can do: 69 | 70 | ``` 71 | math.randomseed(os.time()) 72 | ``` 73 | 74 | but beware, the results of random() will predictable to a attacker. 75 | 76 | Installing 77 | ---------- 78 | 79 | You may simply require src/lua-channels.lua from the source, or install 80 | `lua-channels` from [luarocks](http://luarocks.org). 81 | 82 | -------------------------------------------------------------------------------- /examples/sieve.lua: -------------------------------------------------------------------------------- 1 | local task = require('lua-channels') 2 | 3 | local function counter(c) 4 | local i = 2 5 | while true do 6 | c:send(i) 7 | i = i + 1 8 | end 9 | end 10 | 11 | local function filter(p, recv_ch, send_ch) 12 | while true do 13 | local i = recv_ch:recv() 14 | if i % p ~= 0 then 15 | send_ch:send(i) 16 | end 17 | end 18 | end 19 | 20 | local function sieve(primes_ch) 21 | local c = task.Channel:new() 22 | task.spawn(counter, c) 23 | while true do 24 | local p, newc = c:recv(), task.Channel:new() 25 | primes_ch:send(p) 26 | task.spawn(filter, p, c, newc) 27 | c = newc 28 | end 29 | end 30 | 31 | local function main() 32 | local primes = task.Channel:new() 33 | task.spawn(sieve, primes) 34 | for i = 1, 10 do 35 | print(primes:recv()) 36 | end 37 | end 38 | 39 | task.spawn(main) 40 | task.scheduler() 41 | -------------------------------------------------------------------------------- /examples/test1.lua: -------------------------------------------------------------------------------- 1 | local task = require("lua-channels") 2 | 3 | function counter(c) 4 | local i = 1 5 | while true do 6 | local a = c:send(i) 7 | i = i + 1 8 | end 9 | end 10 | 11 | function a() 12 | local c = task.Channel:new() 13 | task.spawn(counter, c) 14 | 15 | for i = 1, 10 do 16 | local v = c:recv() 17 | print("recv", v) 18 | end 19 | end 20 | 21 | task.spawn(a) 22 | 23 | math.randomseed(os.time()) 24 | task.scheduler() 25 | -------------------------------------------------------------------------------- /examples/test2.lua: -------------------------------------------------------------------------------- 1 | local task = require("lua-channels") 2 | 3 | 4 | function a(c) 5 | -- Blocking send and recv from the same process 6 | local alt = {{c = c, op = task.SEND, p = "from a"}, 7 | {c = c, op = task.RECV}} 8 | 9 | print("a", task.chanalt(alt, true)) 10 | end 11 | 12 | function b(c) 13 | local alt = {{c = c, op = task.SEND, p = "from b"}, 14 | {c = c, op = task.RECV}} 15 | print("b", task.chanalt(alt, true)) 16 | end 17 | 18 | local c = task.Channel:new() 19 | 20 | task.spawn(a, c) 21 | task.spawn(b, c) 22 | 23 | math.randomseed(os.time()) 24 | task.scheduler() 25 | -------------------------------------------------------------------------------- /lua-channels-0.1.0-0.rockspec: -------------------------------------------------------------------------------- 1 | package = "lua-channels" 2 | version = "0.1.0-0" 3 | source = { 4 | url = "https://github.com/majek/lua-channels/archive/v" .. version .. ".tar.gz", 5 | dir = "lua-channels-" .. version, 6 | } 7 | description = { 8 | summary = "Go style channels in pure Lua", 9 | homepage = "https://github.com/majek/lua-channels", 10 | license = "MIT", 11 | maintainer = "Marek Majkowski ", 12 | } 13 | dependencies = { 14 | "lua >= 5.1", 15 | } 16 | build = { 17 | type = "builtin", 18 | modules = { 19 | ["lua-channels"] = "lua-channels.lua" 20 | }, 21 | } 22 | -------------------------------------------------------------------------------- /lua-channels.lua: -------------------------------------------------------------------------------- 1 | ---------------------------------------------------------------------------- 2 | -- Go style Channels for Lua 3 | -- 4 | -- This code is derived from libtask library by Russ Cox, mainly from 5 | -- channel.c. Semantically channels as implemented here are quite 6 | -- similar to channels from the Go language. 7 | -- 8 | -- Usage (we're using unbuffered channel here): 9 | -- 10 | -- local task = require('task') 11 | -- 12 | -- local function counter(channel) 13 | -- local i = 1 14 | -- while true do 15 | -- channel:send(i) 16 | -- i = i + 1 17 | -- end 18 | -- end 19 | -- 20 | -- local function main() 21 | -- local channel = task.Channel:new() 22 | -- task.spawn(counter, channel) 23 | -- assert(channel:recv() == 1) 24 | -- assert(channel:recv() == 2) 25 | -- assert(channel:recv() == 3) 26 | -- end 27 | -- 28 | -- task.spawn(main) 29 | -- task.scheduler() 30 | -- 31 | -- 32 | -- This module exposes: 33 | -- 34 | -- task.spawn(fun, [...]) - run fun as a coroutine with given 35 | -- parameters. You should use this instead of 36 | -- coroutine.create() 37 | -- 38 | -- task.scheduler() - can be run only from the main thread, executes 39 | -- all the stuff, resumes the coroutines that are 40 | -- blocked on channels that became available. You 41 | -- can only do non-blocking sends / receives from 42 | -- the main thread. 43 | -- 44 | -- task.Channel:new([buffer size]) - create a new channel with given size 45 | -- 46 | -- task.chanalt(alts, can_block) - run alt / select / multiplex over 47 | -- the alts structure. For example: 48 | -- 49 | -- task.chanalt({{c = channel_1, op = task.RECV}, 50 | -- {c = channel_2, op = task.SEND, p = "hello"}}, true) 51 | -- 52 | -- This will block current coroutine until it's possible to receive 53 | -- from channel_1 or send to channel_2. chanalt returns a number of 54 | -- statement from alts that succeeded (1 or 2 here) and a received 55 | -- value if executed statement was RECV. 56 | -- 57 | -- Finally, if two alt statements can be fulfilled at the same time, 58 | -- we use math.random() to decide which one should go first. So it 59 | -- makes sense to initialize seed with something random. If you don't 60 | -- have access to an entropy source you can do: 61 | -- math.randomseed(os.time()) 62 | -- but beware, the results of random() will predictable to a attacker. 63 | ---------------------------------------------------------------------------- 64 | 65 | local _M = {} 66 | 67 | -- Constants 68 | local RECV = 0x1 69 | local SEND = 0x2 70 | local NOP = 0x3 71 | 72 | local luajit = not not (package.loaded['jit'] and jit.version_num) 73 | 74 | -- Global objects for scheduler 75 | local tasks_runnable = {} -- list of coroutines ready to be resumed 76 | 77 | 78 | ---------------------------------------------------------------------------- 79 | --- Helpers 80 | 81 | local function random_choice(arr) 82 | if #arr > 1 then 83 | return arr[math.random(#arr)] 84 | else 85 | return arr[1] 86 | end 87 | end 88 | 89 | -- Specialised Set data structure (with random element selection) 90 | local Set = { 91 | new = function(self) 92 | local o = {a = {}, l = {}}; setmetatable(o, self); self.__index = self 93 | return o 94 | end, 95 | 96 | add = function(self, v) 97 | local a, l = self.a, self.l 98 | if a[v] == nil then 99 | table.insert(l, v) 100 | a[v] = #l 101 | return true 102 | end 103 | end, 104 | 105 | remove = function(self, v) 106 | local a, l = self.a, self.l 107 | local i = a[v] 108 | if i > 0 then 109 | local t = l[#l] 110 | a[t], l[i] = i, t 111 | a[i], l[#l] = nil, nil 112 | return true 113 | end 114 | end, 115 | 116 | random = function(self, v) 117 | return random_choice(self.l) 118 | end, 119 | 120 | len = function(self) 121 | return #self.l 122 | end, 123 | } 124 | 125 | -- Circular Buffer data structure 126 | local CircularBuffer = { 127 | new = function(self, size) 128 | local o = {b = {}, slots = size + 1, size = size, l = 0, r = 0} 129 | setmetatable(o, self); self.__index = self 130 | return o 131 | end, 132 | 133 | len = function(self) 134 | return (self.r - self.l) % self.slots 135 | end, 136 | 137 | pop = function(self) 138 | assert(self.l ~= self.r) 139 | local v = self.b[self.l] 140 | self.l = (self.l + 1) % self.slots 141 | return v 142 | end, 143 | 144 | push = function(self, v) 145 | self.b[self.r] = v 146 | self.r = (self.r + 1) % self.slots 147 | assert(self.l ~= self.r) 148 | end, 149 | } 150 | 151 | ---------------------------------------------------------------------------- 152 | -- Scheduling 153 | -- 154 | -- Tasks ready to be run are placed on a stack and it's possible to 155 | -- starve a coroutine. 156 | local function scheduler() 157 | local self_coro, is_main = coroutine.running() 158 | 159 | -- We actually don't care if scheduler is run from the main 160 | -- coroutine. But we do need to make sure that user doesn't do 161 | -- blocking operation from it, as it can't yield. 162 | 163 | -- Be compatible with 5.1 and 5.2 164 | assert(not(self_coro ~= nil and is_main ~= true), 165 | "Scheduler must be run from the main coroutine.") 166 | 167 | local i = 0 168 | while #tasks_runnable > 0 do 169 | local co = table.remove(tasks_runnable) 170 | local okay, emsg = coroutine.resume(co) 171 | if not okay then 172 | error(emsg) 173 | end 174 | i = i + 1 175 | end 176 | return i 177 | end 178 | 179 | local function task_ready(co) 180 | table.insert(tasks_runnable, co) 181 | end 182 | 183 | local function spawn(fun, ...) 184 | local args = {...} 185 | 186 | local f = function() 187 | -- In luajit we could use pcall() here to produce nicer 188 | -- tracebacks on errors, but that won't work on vanilla lua 189 | -- (can't yield from within pcall). 190 | if not luajit then 191 | fun(unpack(args)) 192 | else 193 | local okay, emsg = pcall(fun, unpack(args)) 194 | if not okay then 195 | print(debug.traceback(emsg)) 196 | error(emsg) 197 | end 198 | end 199 | end 200 | local co = coroutine.create(f) 201 | task_ready(co) 202 | end 203 | 204 | ---------------------------------------------------------------------------- 205 | -- Channels - chanalt and helpers 206 | 207 | -- Given two Alts from a single channel exchange data between 208 | -- them. It's implied that one is RECV and another is SEND. Channel 209 | -- may be buffered. 210 | local function altcopy(a, b) 211 | local r, s, c = a, b, a.c 212 | if r.op == SEND then 213 | r, s = s, r 214 | end 215 | 216 | assert(s == nil or s.op == SEND) 217 | assert(r == nil or r.op == RECV) 218 | 219 | -- Channel is empty or unbuffered, copy directly 220 | if s ~= nil and r and c._buf:len() == 0 then 221 | r.alt_array.value = s.p 222 | return 223 | end 224 | 225 | -- Otherwise it's always okay to receive and then send. 226 | if r ~= nil then 227 | r.alt_array.value = c._buf:pop() 228 | end 229 | if s ~= nil then 230 | c._buf:push(s.p) 231 | end 232 | end 233 | 234 | -- Given enqueued alt_array from a chanalt statement remove all alts 235 | -- from the associated channels. 236 | local function altalldequeue(alt_array) 237 | for i = 1, #alt_array do 238 | local a = alt_array[i] 239 | if a.op == RECV or a.op == SEND then 240 | a.c:_get_alts(a.op):remove(a) 241 | end 242 | end 243 | end 244 | 245 | -- Can this Alt be execed without blocking? 246 | local function altcanexec(a) 247 | local c, op = a.c, a.op 248 | if c._buf.size == 0 then 249 | if op ~= NOP then 250 | return c:_get_other_alts(op):len() > 0 251 | end 252 | else 253 | if op == SEND then 254 | return c._buf:len() < c._buf.size 255 | elseif op == RECV then 256 | return c._buf:len() > 0 257 | end 258 | end 259 | end 260 | 261 | -- Alt can be execed so find a counterpart Alt and exec it! 262 | local function altexec(a) 263 | local c, op = a.c, a.op 264 | local other_alts = c:_get_other_alts(op) 265 | local other_a = other_alts:random() 266 | -- other_a may be nil 267 | altcopy(a, other_a) 268 | if other_a ~= nil then 269 | -- Disengage from channels used by the other Alt and make it ready. 270 | altalldequeue(other_a.alt_array) 271 | other_a.alt_array.resolved = other_a.alt_index 272 | task_ready(other_a.alt_array.task) 273 | end 274 | end 275 | 276 | -- The main entry point. Call it `alt` or `select` or just a 277 | -- multiplexing statement. This is user facing function so make sure 278 | -- the parameters passed are sane. 279 | local function chanalt(alt_array, canblock) 280 | assert(#alt_array) 281 | 282 | local list_of_canexec_i = {} 283 | for i = 1, #alt_array do 284 | local a = alt_array[i] 285 | a.alt_array = alt_array 286 | a.alt_index = i 287 | assert(type(a.op) == "number" and 288 | (a.op == RECV or a.op == SEND or a.op == NOP), 289 | "op field must be RECV, SEND or NOP in alt") 290 | assert(type(a.c) == "table" and a.c.__index == _M.Channel, 291 | "pass valid channel to a c field of alt") 292 | if altcanexec(a) == true then 293 | table.insert(list_of_canexec_i, i) 294 | end 295 | end 296 | 297 | if #list_of_canexec_i > 0 then 298 | local i = random_choice(list_of_canexec_i) 299 | altexec(alt_array[i]) 300 | return i, alt_array.value 301 | end 302 | 303 | if canblock ~= true then 304 | return nil 305 | end 306 | 307 | local self_coro, is_main = coroutine.running() 308 | alt_array.task = self_coro 309 | assert(self_coro ~= nil and is_main ~= true, 310 | "Unable to block from the main thread, run scheduler.") 311 | 312 | for i = 1, #alt_array do 313 | local a = alt_array[i] 314 | if a.op ~= NOP then 315 | a.c:_get_alts(a.op):add(a) 316 | end 317 | end 318 | 319 | -- Make sure we're not woken by someone who is not the scheduler. 320 | alt_array.resolved = nil 321 | coroutine.yield() 322 | assert(alt_array.resolved > 0) 323 | 324 | local r = alt_array.resolved 325 | return r, alt_array.value 326 | end 327 | 328 | 329 | ---------------------------------------------------------------------------- 330 | -- Channel object 331 | 332 | local Channel = { 333 | new = function(self, buf_size) 334 | local o = {}; setmetatable(o, self); self.__index = self 335 | o._buf = CircularBuffer:new(buf_size or 0) 336 | o._recv_alts, o._send_alts = Set:new(), Set:new() 337 | return o 338 | end, 339 | 340 | send = function(self, msg) 341 | assert(chanalt({{c = self, op = SEND, p = msg}}, true) == 1) 342 | return true 343 | end, 344 | 345 | recv = function(self) 346 | local alts = {{c = self, op = RECV}} 347 | local s, msg = chanalt(alts, true) 348 | assert(s == 1) 349 | return msg 350 | end, 351 | 352 | nbsend = function(self, msg) 353 | local s = chanalt({{c = self, op = SEND, p = msg}}, false) 354 | return s == 1 355 | end, 356 | 357 | nbrecv = function(self) 358 | local s, msg = chanalt({{c = self, op = RECV}}, false) 359 | return s == 1, msg 360 | end, 361 | 362 | _get_alts = function(self, op) 363 | return (op == RECV) and self._recv_alts or self._send_alts 364 | end, 365 | 366 | _get_other_alts = function(self, op) 367 | return (op == SEND) and self._recv_alts or self._send_alts 368 | end, 369 | 370 | __tostring = function(self) 371 | return string.format("", 372 | self._buf:len(), self._buf.size, self._send_alts:len(), 373 | self._recv_alts:len()) 374 | end, 375 | 376 | __call = function(self) 377 | local function f(s, v) 378 | return true, self:recv() 379 | end 380 | return f, nil, nil 381 | end, 382 | } 383 | 384 | ---------------------------------------------------------------------------- 385 | -- Public interface 386 | 387 | _M.scheduler = scheduler 388 | _M.spawn = spawn 389 | _M.Channel = Channel 390 | _M.chanalt = chanalt 391 | _M.RECV = RECV 392 | _M.SEND = SEND 393 | _M.NOP = NOP 394 | 395 | ---------------------------------------------------------------------------- 396 | ---------------------------------------------------------------------------- 397 | -- Tests 398 | -- 399 | -- To run: 400 | -- $ lua task.lua 401 | 402 | local task = _M 403 | 404 | local tests = { 405 | counter = function () 406 | local done 407 | local function counter(c) 408 | local i = 1 409 | while true do 410 | c:send(i) 411 | i = i + 1 412 | end 413 | end 414 | local function main() 415 | local c = task.Channel:new() 416 | task.spawn(counter, c) 417 | assert(c:recv() == 1) 418 | assert(c:recv() == 2) 419 | assert(c:recv() == 3) 420 | assert(c:recv() == 4) 421 | assert(c:recv() == 5) 422 | done = true 423 | end 424 | task.spawn(main) 425 | task.scheduler() 426 | assert(done) 427 | end, 428 | 429 | nonblocking_channel = function() 430 | local done 431 | local function main() 432 | local b = task.Channel:new() 433 | assert(b:nbsend(1) == false) 434 | assert(b:nbrecv() == false) 435 | 436 | local c = task.Channel:new(1) 437 | assert(c:nbrecv() == false) 438 | assert(c:nbsend(1) == true) 439 | assert(c:nbsend(1) == false) 440 | local r, v = c:nbrecv() 441 | assert(r == true) 442 | assert(v == 1) 443 | assert(c:nbrecv() == false) 444 | done = true 445 | end 446 | task.spawn(main) 447 | task.scheduler() 448 | assert(done) 449 | end, 450 | 451 | concurrent_send_and_recv = function() 452 | local l = {} 453 | local function a(c, name) 454 | -- Blocking send and recv from the same process 455 | local alt = {{c = c, op = task.SEND, p = 1}, 456 | {c = c, op = task.RECV}} 457 | local i, v = task.chanalt(alt, true) 458 | local k = string.format('%s %s', name, i == 1 and "send" or "recv") 459 | l[k] = (l[k] or 0) + 1 460 | end 461 | 462 | for i = 0, 1000 do 463 | -- On Mac OS X in lua 5.1 initializing seed with a 464 | -- predictable value makes no sense. For all seeds from 1 to 465 | -- 1000 the result of math.random(1,3) is _exactly_ the same! 466 | -- So beware, when seeding! 467 | -- math.randomseed(i) 468 | local c = task.Channel:new() 469 | task.spawn(a, c, "a") 470 | task.spawn(a, c, "b") 471 | task.scheduler() 472 | end 473 | 474 | -- Make sure we have randomness, that is: events occur in both 475 | -- orders in 1000 runs 476 | assert(l['a recv'] > 0) 477 | assert(l['a send'] > 0) 478 | assert(l['b recv'] > 0) 479 | assert(l['b send'] > 0) 480 | end, 481 | 482 | channels_from_a_coroutine = function() 483 | local done 484 | local c = task.Channel:new() 485 | local function a() 486 | for i = 1, 100 do 487 | c:send(i) 488 | end 489 | end 490 | local function b() 491 | assert(c:recv() == 1) 492 | assert(c:recv() == 2) 493 | assert(c:recv() == 3) 494 | assert(c:recv() == 4) 495 | assert(c:recv() == 5) 496 | done = true 497 | end 498 | local a_co = coroutine.create(a) 499 | local b_co = coroutine.create(b) 500 | coroutine.resume(a_co) 501 | coroutine.resume(b_co) 502 | task.scheduler() 503 | assert(done) 504 | end, 505 | 506 | fibonacci = function() 507 | local done 508 | local function fib(c) 509 | local x, y = 0, 1 510 | while true do 511 | c:send(x) 512 | x, y = y, x + y 513 | end 514 | end 515 | local function main(c) 516 | assert(c:recv() == 0) 517 | assert(c:recv() == 1) 518 | assert(c:recv() == 1) 519 | assert(c:recv() == 2) 520 | assert(c:recv() == 3) 521 | assert(c:recv() == 5) 522 | assert(c:recv() == 8) 523 | assert(c:recv() == 13) 524 | assert(c:recv() == 21) 525 | assert(c:recv() == 34) 526 | done = true 527 | end 528 | 529 | local c = task.Channel:new() 530 | task.spawn(fib, c) 531 | task.spawn(main, c) 532 | task.scheduler() 533 | assert(done) 534 | end, 535 | 536 | non_blocking_chanalt = function() 537 | local done 538 | local function main() 539 | local c = task.Channel:new() 540 | local alts = {{c = c, op = task.RECV}, 541 | {c = c, op = task.NOP}, 542 | {c = c, op = task.SEND, p = 1}} 543 | assert(task.chanalt(alts, false) == nil) 544 | 545 | local c = task.Channel:new(1) 546 | local alts = {{c = c, op = task.RECV}, 547 | {c = c, op = task.NOP}, 548 | {c = c, op = task.SEND, p = 1}} 549 | assert(task.chanalt(alts, false) == 3) 550 | assert(task.chanalt(alts, false) == 1) 551 | 552 | local alts = {{c = c, op = task.NOP}} 553 | assert(task.chanalt(alts, false) == nil) 554 | 555 | done = true 556 | end 557 | task.spawn(main) 558 | task.scheduler() 559 | assert(done) 560 | end, 561 | 562 | -- Apparently it's not really a Sieve of Eratosthenes: 563 | -- http://www.cs.hmc.edu/~oneill/papers/Sieve-JFP.pdf 564 | eratosthenes_sieve = function() 565 | local done 566 | local function counter(c) 567 | local i = 2 568 | while true do 569 | c:send(i) 570 | i = i + 1 571 | end 572 | end 573 | 574 | local function filter(p, recv_ch, send_ch) 575 | while true do 576 | local i = recv_ch:recv() 577 | if i % p ~= 0 then 578 | send_ch:send(i) 579 | end 580 | end 581 | end 582 | 583 | local function sieve(primes_ch) 584 | local c = task.Channel:new() 585 | task.spawn(counter, c) 586 | while true do 587 | local p, newc = c:recv(), task.Channel:new() 588 | primes_ch:send(p) 589 | task.spawn(filter, p, c, newc) 590 | c = newc 591 | end 592 | end 593 | 594 | local function main() 595 | local primes = task.Channel:new() 596 | task.spawn(sieve, primes) 597 | assert(primes:recv() == 2) 598 | assert(primes:recv() == 3) 599 | assert(primes:recv() == 5) 600 | assert(primes:recv() == 7) 601 | assert(primes:recv() == 11) 602 | assert(primes:recv() == 13) 603 | done = true 604 | end 605 | 606 | task.spawn(main) 607 | task.scheduler() 608 | assert(done) 609 | end, 610 | 611 | channel_as_iterator = function() 612 | local done 613 | local function counter(c) 614 | local i = 2 615 | while true do 616 | c:send(i) 617 | i = i + 1 618 | end 619 | end 620 | 621 | local function main() 622 | local numbers = task.Channel:new() 623 | task.spawn(counter, numbers) 624 | for _, j in numbers() do 625 | if j == 100 then 626 | break 627 | end 628 | done = true 629 | end 630 | end 631 | if _VERSION == "Lua 5.1" and not luajit then 632 | -- sorry, this doesn't work in 5.1 633 | print('skipping... (5.1 unsupported)') 634 | done = true 635 | else 636 | task.spawn(main) 637 | task.scheduler() 638 | end 639 | assert(done) 640 | end, 641 | 642 | } 643 | 644 | -- No parameters: run tests 645 | local args = {...} 646 | if #args == 0 then 647 | print("[*] Running tests...") 648 | local ok, failed = 0, 0 649 | for k, v in pairs(tests) do 650 | print(string.format(' - %s', k)) 651 | v() 652 | ok = ok + 1 653 | end 654 | print(string.format("[*] Successfully run %i tests", ok)) 655 | else 656 | return _M 657 | end 658 | --------------------------------------------------------------------------------