1
0
forked from Mirror/wren

Revise low level fiber semantics to play nicer with schedulers.

Now that I'm starting to write a real async scheduler on top of Wren's
basic fiber API, I have a better feel for what it needs. It turns out
run() is not it.

- Remove run() methods.
- Add transfer() which leaves the caller of the invoked fiber alone.
- Add suspend() to return control to the host application.
- Add Timer.schedule() to start a new independently scheduled fiber.
- Change Timer.sleep() so that it only transfers control to explicitly
  scheduled fibers, not any one.
This commit is contained in:
Bob Nystrom
2015-08-30 22:15:37 -07:00
parent 91af02ac81
commit 556af50f83
49 changed files with 469 additions and 350 deletions

View File

@ -0,0 +1,7 @@
var fiber = Fiber.new {
Fiber.abort("Error!")
IO.print("should not get here")
}
fiber.try()
fiber.call() // expect runtime error: Cannot call an aborted fiber.

View File

@ -1,6 +1,5 @@
var fiber = Fiber.new {
IO.print("fiber")
IO.print("fiber") // expect: fiber
}
var result = fiber.call() // expect: fiber
IO.print(result) // expect: null
IO.print(fiber.call()) // expect: null

View File

@ -1,7 +1,6 @@
var fiber = Fiber.new {
IO.print("fiber")
return "result"
return "result" // expect: fiber
}
var result = fiber.call() // expect: fiber
IO.print(result) // expect: result
IO.print(fiber.call()) // expect: result

View File

@ -0,0 +1,12 @@
var main = Fiber.current
var fiber = Fiber.new {
IO.print("transferred")
IO.print(main.transfer())
IO.print("called")
}
fiber.transfer() // expect: transferred
IO.print("main") // expect: main
fiber.call() // expect: null
// expect: called

View File

@ -0,0 +1,7 @@
var fiber = Fiber.new {
Fiber.abort("Error!")
IO.print("should not get here")
}
fiber.try()
fiber.call("value") // expect runtime error: Cannot call an aborted fiber.

View File

@ -1,12 +1,9 @@
var a
var b
a = Fiber.new {
b.call(3) // expect runtime error: Fiber has already been called.
var A = Fiber.new {
B.call(3) // expect runtime error: Fiber has already been called.
}
b = Fiber.new {
a.call(2)
var B = Fiber.new {
A.call(2)
}
b.call(1)
B.call(1)

View File

@ -0,0 +1,12 @@
var main = Fiber.current
var fiber = Fiber.new {
IO.print("transferred")
IO.print(main.transfer())
IO.print("called")
}
fiber.transfer() // expect: transferred
IO.print("main") // expect: main
fiber.call("value") // expect: value
// expect: called

View File

@ -1,9 +0,0 @@
var fiber = Fiber.new {
IO.print("fiber")
}
IO.print("before") // expect: before
fiber.run() // expect: fiber
// This does not get run since we exit when the run fiber completes.
IO.print("nope")

View File

@ -1,10 +0,0 @@
var fiber
fiber = Fiber.new {
IO.print(1) // expect: 1
fiber.run()
IO.print(2) // expect: 2
}
fiber.call()
IO.print(3) // expect: 3

View File

@ -1,20 +0,0 @@
var a
var b
a = Fiber.new {
IO.print(2)
b.run()
IO.print("nope")
}
b = Fiber.new {
IO.print(1)
a.run()
IO.print(3)
}
b.call()
// expect: 1
// expect: 2
// expect: 3
IO.print(4) // expect: 4

View File

@ -1,13 +0,0 @@
var a = Fiber.new {
IO.print("run")
}
// Run a through an intermediate fiber since it will get discarded and we need
// to return to the main one after a completes.
var b = Fiber.new {
a.run()
IO.print("nope")
}
b.call() // expect: run
a.run() // expect runtime error: Cannot run a finished fiber.

View File

@ -1,11 +0,0 @@
var fiber = Fiber.new {
IO.print("fiber")
}
// The first value passed to the fiber is ignored, since there's no yield call
// to return it.
IO.print("before") // expect: before
fiber.run("ignored") // expect: fiber
// This does not get run since we exit when the run fiber completes.
IO.print("nope")

View File

@ -1,10 +0,0 @@
var fiber
fiber = Fiber.new {
IO.print(1) // expect: 1
fiber.run("ignored")
IO.print(2) // expect: 2
}
fiber.call()
IO.print(3) // expect: 3

View File

@ -0,0 +1,24 @@
var a = Fiber.new {
IO.print("a")
}
var b = Fiber.new {
IO.print("b before")
a.transfer()
IO.print("b after")
}
var c = Fiber.new {
IO.print("c before")
b.transfer()
IO.print("c after")
}
IO.print("start") // expect: start
c.transfer()
// expect: c before
// expect: b before
// expect: a
// Nothing else gets run since the interpreter stops after a completes.

View File

@ -0,0 +1,9 @@
var F = Fiber.new {
IO.print(1) // expect: 1
IO.print(F.transfer()) // expect: null
IO.print(2) // expect: 2
}
F.call()
// F remembers its original caller so transfers back to main.
IO.print(3) // expect: 3

View File

@ -0,0 +1,18 @@
var A = Fiber.new {
IO.print(2)
B.transfer()
IO.print("nope")
}
var B = Fiber.new {
IO.print(1)
A.transfer()
IO.print(3)
}
B.call()
// expect: 1
// expect: 2
// expect: 3
// B remembers its original caller so returns to main.
IO.print(4) // expect: 4

View File

@ -0,0 +1,23 @@
var main = Fiber.current
var fiber = Fiber.new {
IO.print("fiber 1")
IO.print(main.transfer())
// Yield to bounce back to main and clear the caller so we don't get a
// double call() error below.
Fiber.yield()
IO.print(main.transfer())
}
fiber.transfer() // expect: fiber 1
IO.print("main 1") // expect: main 1
fiber.call("call 1") // expect: call 1
IO.print("main 2") // expect: main 2
// Transfer back into the fiber so it has a NULL caller.
fiber.transfer()
fiber.call() // expect: null
IO.print("main 3") // expect: main 3

View File

@ -0,0 +1,13 @@
var main = Fiber.current
var fiber = Fiber.new {
IO.print("fiber")
IO.print(main.transfer())
}
fiber.transfer() // expect: fiber
IO.print("main") // expect: main
fiber.transfer("transfer") // expect: transfer
// This does not get run since we exit when the transferred fiber completes.
IO.print("nope")

View File

@ -0,0 +1,6 @@
var a = Fiber.new {
IO.print("run")
}
a.call() // expect: run
a.transfer() // expect runtime error: Cannot transfer to a finished fiber.

View File

@ -0,0 +1,7 @@
var a = Fiber.new {
Fiber.abort("Error!")
IO.print("should not get here")
}
a.try()
a.transfer() // expect runtime error: Cannot transfer to an aborted fiber.

View File

@ -0,0 +1,9 @@
var fiber = Fiber.new {
IO.print("called")
IO.print(Fiber.yield())
IO.print("transferred")
}
fiber.call() // expect: called
fiber.transfer() // expect: null
// expect: transferred

View File

@ -0,0 +1,24 @@
var a = Fiber.new {
IO.print("a")
}
var b = Fiber.new {
IO.print("b before")
a.transfer("ignored")
IO.print("b after")
}
var c = Fiber.new {
IO.print("c before")
b.transfer("ignored")
IO.print("c after")
}
IO.print("start") // expect: start
c.transfer("ignored")
// expect: c before
// expect: b before
// expect: a
// Nothing else gets run since the interpreter stops after a completes.

View File

@ -0,0 +1,8 @@
var F = Fiber.new {
IO.print(1) // expect: 1
IO.print(F.transfer("value")) // expect: value
IO.print(2) // expect: 2
}
F.call()
IO.print(3) // expect: 3

View File

@ -1,19 +1,16 @@
var a
var b
a = Fiber.new {
var A = Fiber.new {
IO.print(2)
b.run("ignored")
B.transfer("ignored")
IO.print("nope")
}
b = Fiber.new {
var B = Fiber.new {
IO.print(1)
a.run("ignored")
A.transfer("ignored")
IO.print(3)
}
b.call()
B.call()
// expect: 1
// expect: 2
// expect: 3

View File

@ -0,0 +1,6 @@
var a = Fiber.new {
IO.print("run")
}
a.call() // expect: run
a.transfer("blah") // expect runtime error: Cannot transfer to a finished fiber.

View File

@ -0,0 +1,7 @@
var a = Fiber.new {
Fiber.abort("Error!")
IO.print("should not get here")
}
a.try()
a.transfer("blah") // expect runtime error: Cannot transfer to an aborted fiber.

View File

@ -0,0 +1,9 @@
var fiber = Fiber.new {
IO.print("called")
IO.print(Fiber.yield())
IO.print("transferred")
}
fiber.call() // expect: called
fiber.transfer("value") // expect: value
// expect: transferred

View File

@ -6,9 +6,9 @@ var fiber = Fiber.new {
IO.print("fiber 3")
}
var result = fiber.call() // expect: fiber 1
fiber.call() // expect: fiber 1
IO.print("main 1") // expect: main 1
result = fiber.call() // expect: fiber 2
fiber.call() // expect: fiber 2
IO.print("main 2") // expect: main 2
result = fiber.call() // expect: fiber 3
fiber.call() // expect: fiber 3
IO.print("main 3") // expect: main 3

View File

@ -1,9 +1,7 @@
var fiber = Fiber.new {
IO.print("fiber 1")
var result = Fiber.yield()
IO.print(result)
result = Fiber.yield()
IO.print(result)
IO.print(Fiber.yield())
IO.print(Fiber.yield())
}
fiber.call() // expect: fiber 1

View File

@ -1,12 +0,0 @@
var fiber = Fiber.new {
IO.print("fiber")
var result = Fiber.yield()
IO.print(result)
}
fiber.call() // expect: fiber
IO.print("main") // expect: main
fiber.run("run") // expect: run
// This does not get run since we exit when the run fiber completes.
IO.print("nope")

View File

@ -0,0 +1,20 @@
var main = Fiber.current
var a = Fiber.new {
IO.print("a")
IO.print(Fiber.yield())
}
var b = Fiber.new {
IO.print("b")
IO.print(Fiber.yield())
a.call()
a.transfer("value")
}
b.call() // expect: b
b.transfer()
// expect: null
// expect: a
// expect: value

View File

@ -1,11 +1,12 @@
var a = Fiber.new {
IO.print("before") // expect: before
Fiber.yield()
IO.print("not reached")
}
// Run a chain of fibers. Since none of them are called, they all get discarded
// and there is no remaining caller.
var b = Fiber.new { a.run() }
var c = Fiber.new { b.run() }
c.run()
// Transfer through a chain of fibers. Since none of them are called, they all
// get discarded and there is no remaining caller.
var b = Fiber.new { a.transfer() }
var c = Fiber.new { b.transfer() }
c.transfer()
IO.print("not reached")

View File

@ -6,9 +6,9 @@ var fiber = Fiber.new {
IO.print("fiber 3")
}
var result = fiber.call() // expect: fiber 1
IO.print(result) // expect: yield 1
result = fiber.call() // expect: fiber 2
IO.print(result) // expect: yield 2
result = fiber.call() // expect: fiber 3
IO.print(result) // expect: null
IO.print(fiber.call()) // expect: fiber 1
// expect: yield 1
IO.print(fiber.call()) // expect: fiber 2
// expect: yield 2
IO.print(fiber.call()) // expect: fiber 3
// expect: null

View File

@ -1,11 +1,12 @@
var a = Fiber.new {
IO.print("before") // expect: before
Fiber.yield(1)
IO.print("not reached")
}
// Run a chain of fibers. Since none of them are called, they all get discarded
// and there is no remaining caller.
var b = Fiber.new { a.run() }
var c = Fiber.new { b.run() }
c.run()
// Transfer through a chain of fibers. Since none of them are called, they all
// get discarded and there is no remaining caller.
var b = Fiber.new { a.transfer() }
var c = Fiber.new { b.transfer() }
c.transfer()
IO.print("not reached")