From 3c7e028d160b827fa30ed835952dae2ccf72563e Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Thu, 26 Nov 2020 19:00:03 +0200 Subject: [PATCH 001/161] start 2.0.0 serie --- 0.99.x/README.md | 542 ++++++++++++++++++ 0.99.x/build.sbt | 71 +++ 0.99.x/project/plugins.sbt | 3 + .../scala/gopher/ChannelClosedException.scala | 0 .../src}/main/scala/gopher/Defers.scala | 0 .../main/scala/gopher/FlowTermination.scala | 0 .../src}/main/scala/gopher/Gopher.scala | 0 .../src}/main/scala/gopher/GopherAPI.scala | 0 .../main/scala/gopher/ThreadingPolicy.scala | 0 .../src}/main/scala/gopher/Time.scala | 0 .../src}/main/scala/gopher/Transputer.scala | 0 .../gopher/channels/ActorBackedChannel.scala | 0 .../channels/BaseBufferedChannelActor.scala | 0 .../channels/BufferedChannelActor.scala | 0 .../main/scala/gopher/channels/Channel.scala | 0 .../scala/gopher/channels/ChannelActor.scala | 0 .../gopher/channels/ChannelActorMessage.scala | 0 .../gopher/channels/ChannelProcessor.scala | 0 .../gopher/channels/ChannelSupervisor.scala | 0 .../gopher/channels/CloseableInput.scala | 0 .../scala/gopher/channels/Continuated.scala | 0 .../channels/CurrentFlowTermination.scala | 0 .../scala/gopher/channels/DoneProvider.scala | 0 .../scala/gopher/channels/DuppedInput.scala | 0 .../gopher/channels/EffectedChannel.scala | 0 .../scala/gopher/channels/EffectedInput.scala | 0 .../gopher/channels/EffectedOutput.scala | 0 .../scala/gopher/channels/ExpireChannel.scala | 0 .../gopher/channels/FoldSelectorBuilder.scala | 0 .../channels/ForeverSelectorBuilder.scala | 0 .../scala/gopher/channels/FutureInput.scala | 0 .../gopher/channels/GopherAPIProvider.scala | 0 .../channels/GrowingBufferedChannel.scala | 0 .../main/scala/gopher/channels/Input.scala | 0 .../scala/gopher/channels/InputOutput.scala | 0 .../channels/InputSelectorBuilder.scala | 0 .../gopher/channels/InputWithTimeouts.scala | 0 .../scala/gopher/channels/LazyChannel.scala | 0 .../gopher/channels/OnceSelectorBuilder.scala | 0 .../gopher/channels/OneTimeChannel.scala | 0 .../main/scala/gopher/channels/OrInput.scala | 0 .../main/scala/gopher/channels/Output.scala | 0 .../gopher/channels/OutputWithTimeouts.scala | 0 .../channels/PromiseFlowTermination.scala | 0 .../main/scala/gopher/channels/Selector.scala | 0 .../gopher/channels/SelectorArguments.scala | 0 .../gopher/channels/SelectorBuilder.scala | 0 .../gopher/channels/SelectorFactory.scala | 0 .../channels/UnbufferedChannelActor.scala | 0 .../scala/gopher/channels/ZippedInput.scala | 0 .../main/scala/gopher/channels/package.scala | 0 .../scala/gopher/goasync/AsyncApply.scala | 0 .../scala/gopher/goasync/AsyncIterable.scala | 0 .../scala/gopher/goasync/AsyncOption.scala | 0 .../scala/gopher/goasync/AsyncWrapper.scala | 0 .../main/scala/gopher/goasync/GoAsync.scala | 0 .../src}/main/scala/gopher/package.scala | 0 .../transputers/ReplicateTransputer.scala | 0 .../transputers/TransputerSupervisor.scala | 0 .../scala/gopher/transputers/package.scala | 0 .../main/scala/gopher/util/ASTUtilImpl.scala | 0 .../main/scala/gopher/util/Effected.scala | 0 .../scala/gopher/util/IntIndexedReverse.scala | 0 .../main/scala/gopher/util/MacroUtil.scala | 0 .../main/scala/gopher/util/ReflectUtil.scala | 0 .../src}/test/resources/application.conf | 0 .../test/scala/example/BetterSieveSuite.scala | 0 .../src}/test/scala/example/Bingo.scala | 0 .../test/scala/example/BroadcasterSuite.scala | 0 .../src}/test/scala/example/CopyFile.scala | 0 .../scala/example/FibonaccyAsyncSuite.scala | 0 .../FibonaccyAsyncUnsugaredSuite.scala | 0 .../scala/example/FibonaccyFoldSuite.scala | 0 .../test/scala/example/FibonaccySuite.scala | 0 .../src}/test/scala/example/SieveSuite.scala | 0 .../gopher/channels/AsyncSelectSuite.scala | 0 .../gopher/channels/ChannelCleanupSuite.scala | 0 .../gopher/channels/ChannelCloseSuite.scala | 0 .../gopher/channels/CommonTestObjects.scala | 0 .../gopher/channels/DuppedChannelsSuite.scala | 0 .../gopher/channels/ExpireChannelSuite.scala | 0 .../channels/FlowTerminationSuite.scala | 0 .../gopher/channels/FoldSelectSuite.scala | 0 .../gopher/channels/IOComposeSuite.scala | 0 .../gopher/channels/IOTimeoutsSuite.scala | 0 .../scala/gopher/channels/InputOpsSuite.scala | 0 .../gopher/channels/MacroSelectSuite.scala | 0 .../gopher/channels/ReadCoroutinesSuite.scala | 0 .../channels/SchedulerStartupTest.scala | 0 .../gopher/channels/SelectErrorSuite.scala | 0 .../scala/gopher/channels/SelectSuite.scala | 0 .../gopher/channels/SelectTimeoutSuite.scala | 0 .../channels/UnbufferedSelectSuite.scala | 0 .../hofasyn/FibbonacyAsyncLoopSuite.scala | 0 .../scala/gopher/hofasyn/HofAsyncSuite.scala | 0 .../gopher/internal/FoldParseSuite.scala | 0 .../test/scala/gopher/scope/DefersSuite.scala | 0 .../scala/gopher/scope/GoWithDeferSuite.scala | 0 .../scala/gopher/scope/ScopeMacroSuite.scala | 0 .../gopher/scope/SimpleStatementSuite.scala | 0 .../src}/test/scala/gopher/tags/Gen.scala | 0 .../src}/test/scala/gopher/tags/Now.scala | 0 .../test/scala/gopher/time/TimeSuite.scala | 0 .../gopher/transputers/ReplicateSuite.scala | 0 .../transputers/TransputerRestartTest.scala | 0 NOTICE | 2 +- build.sbt | 103 ++-- {notes => docs/history/notes}/0.99.1.markdown | 0 .../history/notes}/0.99.12.markdown | 0 {notes => docs/history/notes}/0.99.2.markdown | 0 {notes => docs/history/notes}/0.99.3.markdown | 0 {notes => docs/history/notes}/0.99.4.markdown | 0 {notes => docs/history/notes}/0.99.5.markdown | 0 {notes => docs/history/notes}/0.99.6.markdown | 0 {notes => docs/history/notes}/0.99.7.markdown | 0 {notes => docs/history/notes}/0.99.8.markdown | 0 {notes => docs/history/notes}/0.99.9.markdown | 0 {notes => docs/history/notes}/about.markdown | 0 {notes => docs/history/notes}/papers.markdown | 0 {notes => docs/history/notes}/techreport.pdf | Bin {notes => docs/history}/techreport.bib | 0 {notes => docs/history}/techreport.tex | 0 .../gopher/impl/MTUnbufferedChannel.scala | 86 +++ project/build.properties | 1 + project/plugins.sbt | 10 +- shared/src/main/scala/gopher/Channel.scala | 14 + .../scala/gopher/ChannelClosedException.scala | 3 + shared/src/main/scala/gopher/Expirable.scala | 33 ++ shared/src/main/scala/gopher/IChannel.scala | 46 ++ shared/src/main/scala/gopher/OChannel.scala | 31 + shared/src/main/scala/gopher/Reader.scala | 7 + .../src/main/scala/gopher/SelectGroup.scala | 70 +++ shared/src/main/scala/gopher/Writer.scala | 6 + 133 files changed, 956 insertions(+), 72 deletions(-) create mode 100644 0.99.x/README.md create mode 100644 0.99.x/build.sbt create mode 100644 0.99.x/project/plugins.sbt rename {src => 0.99.x/src}/main/scala/gopher/ChannelClosedException.scala (100%) rename {src => 0.99.x/src}/main/scala/gopher/Defers.scala (100%) rename {src => 0.99.x/src}/main/scala/gopher/FlowTermination.scala (100%) rename {src => 0.99.x/src}/main/scala/gopher/Gopher.scala (100%) rename {src => 0.99.x/src}/main/scala/gopher/GopherAPI.scala (100%) rename {src => 0.99.x/src}/main/scala/gopher/ThreadingPolicy.scala (100%) rename {src => 0.99.x/src}/main/scala/gopher/Time.scala (100%) rename {src => 0.99.x/src}/main/scala/gopher/Transputer.scala (100%) rename {src => 0.99.x/src}/main/scala/gopher/channels/ActorBackedChannel.scala (100%) rename {src => 0.99.x/src}/main/scala/gopher/channels/BaseBufferedChannelActor.scala (100%) rename {src => 0.99.x/src}/main/scala/gopher/channels/BufferedChannelActor.scala (100%) rename {src => 0.99.x/src}/main/scala/gopher/channels/Channel.scala (100%) rename {src => 0.99.x/src}/main/scala/gopher/channels/ChannelActor.scala (100%) rename {src => 0.99.x/src}/main/scala/gopher/channels/ChannelActorMessage.scala (100%) rename {src => 0.99.x/src}/main/scala/gopher/channels/ChannelProcessor.scala (100%) rename {src => 0.99.x/src}/main/scala/gopher/channels/ChannelSupervisor.scala (100%) rename {src => 0.99.x/src}/main/scala/gopher/channels/CloseableInput.scala (100%) rename {src => 0.99.x/src}/main/scala/gopher/channels/Continuated.scala (100%) rename {src => 0.99.x/src}/main/scala/gopher/channels/CurrentFlowTermination.scala (100%) rename {src => 0.99.x/src}/main/scala/gopher/channels/DoneProvider.scala (100%) rename {src => 0.99.x/src}/main/scala/gopher/channels/DuppedInput.scala (100%) rename {src => 0.99.x/src}/main/scala/gopher/channels/EffectedChannel.scala (100%) rename {src => 0.99.x/src}/main/scala/gopher/channels/EffectedInput.scala (100%) rename {src => 0.99.x/src}/main/scala/gopher/channels/EffectedOutput.scala (100%) rename {src => 0.99.x/src}/main/scala/gopher/channels/ExpireChannel.scala (100%) rename {src => 0.99.x/src}/main/scala/gopher/channels/FoldSelectorBuilder.scala (100%) rename {src => 0.99.x/src}/main/scala/gopher/channels/ForeverSelectorBuilder.scala (100%) rename {src => 0.99.x/src}/main/scala/gopher/channels/FutureInput.scala (100%) rename {src => 0.99.x/src}/main/scala/gopher/channels/GopherAPIProvider.scala (100%) rename {src => 0.99.x/src}/main/scala/gopher/channels/GrowingBufferedChannel.scala (100%) rename {src => 0.99.x/src}/main/scala/gopher/channels/Input.scala (100%) rename {src => 0.99.x/src}/main/scala/gopher/channels/InputOutput.scala (100%) rename {src => 0.99.x/src}/main/scala/gopher/channels/InputSelectorBuilder.scala (100%) rename {src => 0.99.x/src}/main/scala/gopher/channels/InputWithTimeouts.scala (100%) rename {src => 0.99.x/src}/main/scala/gopher/channels/LazyChannel.scala (100%) rename {src => 0.99.x/src}/main/scala/gopher/channels/OnceSelectorBuilder.scala (100%) rename {src => 0.99.x/src}/main/scala/gopher/channels/OneTimeChannel.scala (100%) rename {src => 0.99.x/src}/main/scala/gopher/channels/OrInput.scala (100%) rename {src => 0.99.x/src}/main/scala/gopher/channels/Output.scala (100%) rename {src => 0.99.x/src}/main/scala/gopher/channels/OutputWithTimeouts.scala (100%) rename {src => 0.99.x/src}/main/scala/gopher/channels/PromiseFlowTermination.scala (100%) rename {src => 0.99.x/src}/main/scala/gopher/channels/Selector.scala (100%) rename {src => 0.99.x/src}/main/scala/gopher/channels/SelectorArguments.scala (100%) rename {src => 0.99.x/src}/main/scala/gopher/channels/SelectorBuilder.scala (100%) rename {src => 0.99.x/src}/main/scala/gopher/channels/SelectorFactory.scala (100%) rename {src => 0.99.x/src}/main/scala/gopher/channels/UnbufferedChannelActor.scala (100%) rename {src => 0.99.x/src}/main/scala/gopher/channels/ZippedInput.scala (100%) rename {src => 0.99.x/src}/main/scala/gopher/channels/package.scala (100%) rename {src => 0.99.x/src}/main/scala/gopher/goasync/AsyncApply.scala (100%) rename {src => 0.99.x/src}/main/scala/gopher/goasync/AsyncIterable.scala (100%) rename {src => 0.99.x/src}/main/scala/gopher/goasync/AsyncOption.scala (100%) rename {src => 0.99.x/src}/main/scala/gopher/goasync/AsyncWrapper.scala (100%) rename {src => 0.99.x/src}/main/scala/gopher/goasync/GoAsync.scala (100%) rename {src => 0.99.x/src}/main/scala/gopher/package.scala (100%) rename {src => 0.99.x/src}/main/scala/gopher/transputers/ReplicateTransputer.scala (100%) rename {src => 0.99.x/src}/main/scala/gopher/transputers/TransputerSupervisor.scala (100%) rename {src => 0.99.x/src}/main/scala/gopher/transputers/package.scala (100%) rename {src => 0.99.x/src}/main/scala/gopher/util/ASTUtilImpl.scala (100%) rename {src => 0.99.x/src}/main/scala/gopher/util/Effected.scala (100%) rename {src => 0.99.x/src}/main/scala/gopher/util/IntIndexedReverse.scala (100%) rename {src => 0.99.x/src}/main/scala/gopher/util/MacroUtil.scala (100%) rename {src => 0.99.x/src}/main/scala/gopher/util/ReflectUtil.scala (100%) rename {src => 0.99.x/src}/test/resources/application.conf (100%) rename {src => 0.99.x/src}/test/scala/example/BetterSieveSuite.scala (100%) rename {src => 0.99.x/src}/test/scala/example/Bingo.scala (100%) rename {src => 0.99.x/src}/test/scala/example/BroadcasterSuite.scala (100%) rename {src => 0.99.x/src}/test/scala/example/CopyFile.scala (100%) rename {src => 0.99.x/src}/test/scala/example/FibonaccyAsyncSuite.scala (100%) rename {src => 0.99.x/src}/test/scala/example/FibonaccyAsyncUnsugaredSuite.scala (100%) rename {src => 0.99.x/src}/test/scala/example/FibonaccyFoldSuite.scala (100%) rename {src => 0.99.x/src}/test/scala/example/FibonaccySuite.scala (100%) rename {src => 0.99.x/src}/test/scala/example/SieveSuite.scala (100%) rename {src => 0.99.x/src}/test/scala/gopher/channels/AsyncSelectSuite.scala (100%) rename {src => 0.99.x/src}/test/scala/gopher/channels/ChannelCleanupSuite.scala (100%) rename {src => 0.99.x/src}/test/scala/gopher/channels/ChannelCloseSuite.scala (100%) rename {src => 0.99.x/src}/test/scala/gopher/channels/CommonTestObjects.scala (100%) rename {src => 0.99.x/src}/test/scala/gopher/channels/DuppedChannelsSuite.scala (100%) rename {src => 0.99.x/src}/test/scala/gopher/channels/ExpireChannelSuite.scala (100%) rename {src => 0.99.x/src}/test/scala/gopher/channels/FlowTerminationSuite.scala (100%) rename {src => 0.99.x/src}/test/scala/gopher/channels/FoldSelectSuite.scala (100%) rename {src => 0.99.x/src}/test/scala/gopher/channels/IOComposeSuite.scala (100%) rename {src => 0.99.x/src}/test/scala/gopher/channels/IOTimeoutsSuite.scala (100%) rename {src => 0.99.x/src}/test/scala/gopher/channels/InputOpsSuite.scala (100%) rename {src => 0.99.x/src}/test/scala/gopher/channels/MacroSelectSuite.scala (100%) rename {src => 0.99.x/src}/test/scala/gopher/channels/ReadCoroutinesSuite.scala (100%) rename {src => 0.99.x/src}/test/scala/gopher/channels/SchedulerStartupTest.scala (100%) rename {src => 0.99.x/src}/test/scala/gopher/channels/SelectErrorSuite.scala (100%) rename {src => 0.99.x/src}/test/scala/gopher/channels/SelectSuite.scala (100%) rename {src => 0.99.x/src}/test/scala/gopher/channels/SelectTimeoutSuite.scala (100%) rename {src => 0.99.x/src}/test/scala/gopher/channels/UnbufferedSelectSuite.scala (100%) rename {src => 0.99.x/src}/test/scala/gopher/hofasyn/FibbonacyAsyncLoopSuite.scala (100%) rename {src => 0.99.x/src}/test/scala/gopher/hofasyn/HofAsyncSuite.scala (100%) rename {src => 0.99.x/src}/test/scala/gopher/internal/FoldParseSuite.scala (100%) rename {src => 0.99.x/src}/test/scala/gopher/scope/DefersSuite.scala (100%) rename {src => 0.99.x/src}/test/scala/gopher/scope/GoWithDeferSuite.scala (100%) rename {src => 0.99.x/src}/test/scala/gopher/scope/ScopeMacroSuite.scala (100%) rename {src => 0.99.x/src}/test/scala/gopher/scope/SimpleStatementSuite.scala (100%) rename {src => 0.99.x/src}/test/scala/gopher/tags/Gen.scala (100%) rename {src => 0.99.x/src}/test/scala/gopher/tags/Now.scala (100%) rename {src => 0.99.x/src}/test/scala/gopher/time/TimeSuite.scala (100%) rename {src => 0.99.x/src}/test/scala/gopher/transputers/ReplicateSuite.scala (100%) rename {src => 0.99.x/src}/test/scala/gopher/transputers/TransputerRestartTest.scala (100%) rename {notes => docs/history/notes}/0.99.1.markdown (100%) rename {notes => docs/history/notes}/0.99.12.markdown (100%) rename {notes => docs/history/notes}/0.99.2.markdown (100%) rename {notes => docs/history/notes}/0.99.3.markdown (100%) rename {notes => docs/history/notes}/0.99.4.markdown (100%) rename {notes => docs/history/notes}/0.99.5.markdown (100%) rename {notes => docs/history/notes}/0.99.6.markdown (100%) rename {notes => docs/history/notes}/0.99.7.markdown (100%) rename {notes => docs/history/notes}/0.99.8.markdown (100%) rename {notes => docs/history/notes}/0.99.9.markdown (100%) rename {notes => docs/history/notes}/about.markdown (100%) rename {notes => docs/history/notes}/papers.markdown (100%) rename {notes => docs/history/notes}/techreport.pdf (100%) rename {notes => docs/history}/techreport.bib (100%) rename {notes => docs/history}/techreport.tex (100%) create mode 100644 jvm/src/main/scala/gopher/impl/MTUnbufferedChannel.scala create mode 100644 project/build.properties create mode 100644 shared/src/main/scala/gopher/Channel.scala create mode 100644 shared/src/main/scala/gopher/ChannelClosedException.scala create mode 100644 shared/src/main/scala/gopher/Expirable.scala create mode 100644 shared/src/main/scala/gopher/IChannel.scala create mode 100644 shared/src/main/scala/gopher/OChannel.scala create mode 100644 shared/src/main/scala/gopher/Reader.scala create mode 100644 shared/src/main/scala/gopher/SelectGroup.scala create mode 100644 shared/src/main/scala/gopher/Writer.scala diff --git a/0.99.x/README.md b/0.99.x/README.md new file mode 100644 index 00000000..b47eb43f --- /dev/null +++ b/0.99.x/README.md @@ -0,0 +1,542 @@ + +## Gopher: asynchronous implementation of go-like channels/selectors in scala +======= + +### Dependences: + + * scala 2.13.3 + * akka 2.6.8 + * scala-async + +#### Download: + +For scala 2.13: + + libraryDependencies += "com.github.rssh" %% "scala-gopher" % "0.99.15" + +(or `0.99.16-SNAPSHOT` for development version). + +For scala 2.12: + + libraryDependencies += "com.github.rssh" %% "scala-gopher" % "0.99.10" + + +Scala-gopher is open source (license is Apache2); binaries are available from the maven-central repository. + +## Overview + + Scala-gopher is a scala library, build on top of Akka and SIP-22 async, which provide an implementation of + CSP [Communicate Sequential Processes] primitives, known as 'Go-like channels.' Also, analogs of go/defer/recover control-flow constructions are provided. + +Note, which this is not an emulation of go language structures in Scala, but rather a reimplementation of the main ideas in 'scala-like' manner. + + + +### Initialization + + You need an instance of gopherApi for creating channels and selectors. The easiest way is to use one as Akka extension: + + import akka.actors._ + import gopher._ + + ...... + + val actorSystem = ActorSystem.create("system") + val gopherApi = Gopher(actorSystem) + + In akka.conf we can place config values in 'gopher' entry. + +## Control flow constructions: + +### goScope + + `goScope[T](body: =>T)` is expression, which allows to use inside `body` go-like 'defer' and 'recover' expression. + + Typical usage: +~~~ scala +import gopher._ +import java.io._ + +object CopyFile { + + def main(args: Array[String]): Unit = { + if (args.length != 3) { + System.err.println("usage: copy in out"); + } + copy(new File(args(1)), new File(args(2))) + } + + def copy(inf: File, outf: File): Long = + goScope { + val in = new FileInputStream(inf) + defer { + in.close() + } + val out = new FileOutputStream(outf); + defer { + out.close() + } + out.getChannel() transferFrom(in.getChannel(), 0, Long.MaxValue) + } + +} +~~~ + Here statements inside defer block executed at the end of goScope block in reverse order. + + Inside goScope we can use two pseudo functions: + +* `defer(body: =>Unit):Unit` - defer execution of `body` until the end of `go` or `goScope` block and previous defered blocks. +* `recover[T](f:PartialFunction[Throwable,T]):Boolean` -- can be used only within `defer` block with next semantics: +* * if exception was raised inside `go` or `goScope` than `recover` try to apply `f` to this exception and +* * * if `f` is applicable - set `f(e)` as return value of the block and return true +* * * otherwise - do nothing and return false +* * during normal exit - return false. + +You can look on `defer` as on stackable finally clauses, and on `defer` with `recover` inside as on `catch` clause. Small example: + +~~~ scala +val s = goScope{ + defer{ recover{ + case ex: Throwable => "CCC" + } } + throw new Exception("") + "QQQ" + } +~~~ + + will set `s` to "CCC". + + + +### go + + `go[T](body: =>T)(implicit ex:ExecutionContext):Future[T]` starts asynchronous execution of `body` in provided execution context. Inside go we can use `defer`/`recover` clauses and blocked read/write channel operations. + + Go implemented on top of [SIP-22](http://docs.scala-lang.org/sips/pending/async.html) async and share the same limitations. In addition to async/await transfoirm `go` provide lifting up asynchronous expressions inside some well-known hight-order functions (i.e. it is possible to use async operations inside for loops). Details are available in the tech report: https://arxiv.org/abs/1611.00602 . + + +## Channels + +Channels are used for asynchronous communication between execution flows. + +When using channel inside *go* block, you can look at one as on classic blocked queue with fixed size with methods read and write: + + + val channel = gopherApi.makeChannel[Int]; + + go { + channel.write(a) + } + ...... + go { + val i = channel.read + } + + + +* `channel.write(x)` - send x to channel and wait until one will be sent (it is possible us as synonyms `channel<~x` and `channel!x` if you prefer short syntax) +* `channel.read` or `(channel ?)` - blocking read + +Blocking operations can be used only inside `go` or `Async.await` blocks. + +Outside we can use asynchronous version: + +* `channel.awrite(x)` will write `x` and return to us `Future[Unit]` which will be executed after x will send +* `channel.aread` will return future to the value, which will be read. + +Also, channels can be closed. After this attempt to write will cause throwing 'ClosedChannelException.' Reading will be still possible up to 'last written value', after this attempt to read will cause the same exception. Also, each channel provides `done` input for firing close events. + +Note, closing channels are not mandatory; unreachable channels are garbage-collected regardless of they are closed or not. + + +Channels can be buffered and unbuffered. In a unbuffered channel, write return control to the caller after another side actually will start processing; buffered channel force provider to wait only if internal channel buffer is full. + +Also, you can use only `Input` or `Output` interfaces, where an appropriative read/write operations are defined. +For `Input`, exists usual collection functions, like `map`, `zip`, `takeN`, `fold` ... etc. Scala Iterable can be represented as `channels.Input` via method `gopherApi.iterableInput`. Also, we can use Scala futures as channels, which produce one value and then closes. For obtaining such input use `gopherApi.futureInput`. + +`|` (i.e. or) operator used for merged inputs, i.e. `(x|y).read` will read a value from channel x or y when one will be available. + +For each input and output you can create a facility with tracked timeout, i.e. if `in` is input, then +~~~ scala + val (inReady, inTimeouts) = in.withInputTimeouts(10 seconds) +~~~ +will return two inputs, where reading from `inReady` will return the same as reading from `in`. And if waiting for reading takes longer than 10 seconds then the value of timeout will be available in `inTimeouts`. Analogically we can create output with timeouts: +~~~ scala + val (outReady, outTimeouts) = out.withOutputTimeouts(10 seconds) +~~~ + + +Also, note that you can provide own Input and Output implementations by implementing callback `cbread` and `cbwrite` methods. + + +## Select loops and folds + + 'select statement' is somewhat similar to Unix 'select' syscall: + from a set of blocking operations select one who is ready to input/output and run it. + + The usual pattern of channel processing in go language is to wrap select operation into an endless loop. + + Gopher provides similar functionality: + +~~~ scala +go{ + for( s <- gopherApi.select.forever) + s match { + case i:channelA.read => ..do-something-with-i + case ch:channelB.read .. do-something-with-b + } +} +~~~ + + Here we read in the loop from channelA or channelB. + + Body of select loop must consist only of one `match` statement where + left parts in `case` clauses must have the following form + + * `v:channel.read` (for reading from channel) + * `v:Tye if (v==read(ch))` (for reading from channel or future) + * `v:channel.write if (v==expr)` (for writing `expr` into channel). + * `v:Type if (v==write(ch,expr))` (for writing `expr` into channel). + * `_` - for 'idle' action. + + + For endless loop inside `go` we can use the shortcut with the syntax of partial function: + +~~~ scala + gopherApi.select.forever{ + case i:channelA.read => ... do-something-with-i + case ch:channelB.read ... do-something-with-b + } +~~~ + + + Inside case actions, we can use blocking read/writes and await operations. Call of doExit in the implicit instance of `FlowTermination[T]` (for a forever loop this is `FlowTermination[Unit]`) can be used for exiting from the loop; `select.exit` and `select.shutdown` macroses are shortcuts for this. + + Example: + +~~~ scala +val channel = gopherApi.makeChannel[Int](100) + +val producer = channel.awrite(1 to 1000) + +@volatile var sum = 0; +val consumer = gopherApi.select.forever{ + case i: channerl.read => + sum = sum + i + if (i==1000) { + select.shutdown() + } +} + +Await.ready(consumer, 5.second) +~~~ + + A combination of variable and select loop better modeled with help 'fold over select' construction: + +~~~ scala +val sum = gopherApi.select.afold(0) { (state, selector) => + selector match { + case i: channel.read => + val nstate = state + i + if (i==1000) { + select.exit(nstate) + } + nstate + } +} +~~~ + + + More than one variables in state can be modeled with partial function case syntax: + +~~~ scala +val fib = select.afold((0,1)) { case ((x,y), s) => + s match { + case x:channel.write => (y,y+x) + case q:quit.read => select.exit((x,y)) + } +} +~~~ + + Also, we can use 'map over select' to represent results of handling of different events as input side of a channel: + +~~~ scala +val multiplexed = select amap { + case x:ch1.read => (s1,x) + case y:ch2.read => (s2,y) + } +~~~ + + + For using select operation not enclosed in a loop, scala-gopher provide + *select.once* syntax: + +~~~ scala +gopherApi.select.once{ + case i: channelA.read => s"Readed(${i})" + case x:channelB.write if (x==1) => s"Written(${x})" +} +~~~ + + + Such form can be called from any environment and will return `Future[String]`. Inside `go` you can wrap this in await of use 'for' syntax as with `forever`. + +~~~ scala +go { + ..... + val s = for(s <-gopherApi.select.once) + s match { + case i: channelA.read => s"Readed(${i})" + case x: channelB.write if (x==1) => s"Written(${x})" + } + +} +~~~ + + + and afold become fold: + +~~~ scala +go { + ... + val sum = select.fold(0) { (n,s) => + s match { + case x: channelA.read => n+x + case q: quit.read => select.exit(n) + } + } +} +~~~ + + amap - map + +~~~ scala +val multiplexed = for(s <- select) yield + s match { + case x:ch1.read => (s1,x) + case y:ch2.read => (s2,y) + } +~~~ + +## Done signals. + + Sometimes it is useful to receive a message when some `Input` becomes closed. Such inputs are named 'CloseableInputs' and provides a way to receive close notification in selector using `done` pseudo-type. + +~~~ scala + select.foreach{ + case x:ch.read => Console.println(s"received: ${x}") + case _:ch.done => Console.println(s"done") + select.exit(()) + } +~~~ + + Note, that you must exit from current flow in `done` handler, otherwise `done` signals will be intensively generated in a loop. + + +## Effected{Input,Output,Channel} + + One useful programming pattern, often used in CSP-style programming: have a channel from wich we read (or to where we write) as a part of a state. In Go language, this is usually modelled as a mutable variable, changed inside the same select statement, where one is read/written. + + In scala-gopher, we have the ability to use a technique of 'EffectedChannel', which can be seen as an entity, which holds channel, can be used in read/write and can be changed only via effect (operation, which accepts the previous state and returns the next). + +Let's look at the example: + +~~~ scala + def generate(n:Int, quit:Promise[Boolean]):Channel[Int] = + { + val channel = makeChannel[Int]() + channel.awriteAll(2 to n) andThen (_ => quit success true) + channel + } + + def filter(in:Channel[Int]):Input[Int] = + { + val filtered = makeChannel[Int]() + val sieve = makeEffectedInput(in) + sieve.aforeach { prime => + sieve <<= (_.filter(_ % prime != 0)) + filtered <~ prime + } + filtered + } +~~~ + +Here in 'filter', we generate a set of prime numbers, and make a sieve of Eratosthenes by sequentially applying 'filter' effect to state of sieve EffectedInput. + + +## Transputers + + The logic of data transformation between channels can be encapsulated in special `Transputer` concept. (Word 'transputer' was chosen + as a reminder about INMOS processor, for which one of the first CSP languages, Occam, was developed). You can view on transputer as + a representation of a restartable process that consists from: + + * Set of named input and output ports. + * Logic for propagating information from the input to the output ports. + * Possible state + * Logic of error recovering. + +I.e. we saw that Transputer is similar to Actor with the following difference: + When Actor provides reaction to incoming messages from the mailbox and sending signals to other actors, Transputers provide processing of incoming messages from input ports and sending outcoming messages to output ports. When operations inside Actor must not be blocked, operations inside Transputer can wait. + +Transformers are build hierarchically with the help of 3 operations: + + * select (logic is execution of a select statement ) + * parallel combination (logic is parallel execution of parts) + * replication (logic is a parallel execution of a set of identical transformers.) + +### Select transputer + + Let's look at a simple example: transputer with two input ports and one output. +When the same number has come from `inA` and `inB`, then +transputer prints `Bingo` on console and output this number to `out`: + +~~~ scala + trait BingoTransputer extends SelectTransputer + { + val inA = InPort[Int] + val inB = InPort[Int] + val out = OutPort[Boolean] + + loop { + case x:inA.read => + y = inB.read + out.write(x==y) + if (x==y) { + Console.println(s"Bingo: ${x}") + } + } + + } +~~~ + + A select loop is described in `loop` statement. + + To create transputer we can use `gopherApi.makeTransputer` call: +~~~ scala +val bing = gopherApi.makeTransputer[BingoTransputer] +~~~ + after the creation of transputer, we can create channels, connect one to ports and start transformer. + +~~~ scala +val inA = makeChannel[Int]() +bingo.inA.connect(inA) +val inB = makeChannel[Int]() +bingo.inB.connect(inB) +val out = makeChannel[Int]() +bingo.out.connect(out) + +val shutdownFuture = bingo.start() +~~~ + + + Then after we will write to `inA` and `inB` values `(1,1)` then `true` will become available for reading from `out`. + +#### Error recovery + + On an exception from a loop statement, transputer will be restarted with ports, connected to the same channels. Such behavior is the default; we can configure one by setting recovery policy: + +~~~ scala +val t = makeTransputer[MyType].recover { + case ex: MyException => SupervisorStrategy.Escalate + } +~~~ + + Recovery policy is a partial function from throwable to akka `SupervisorStrategy.Direction`. Escalated exceptions are passed to parent transputers or to TransputerSupervisor actor, which handle failures according to akka default supervisor strategy. + + How many times transputer can be restarted within given period can be configured via failureLimit call: + +~~~ scala + t.failureLimit(maxFailures = 20, windowDuration = 10 seconds) +~~~ + + This setting means that if 20 failures will occur during 10 seconds, then exception Transputer.TooManyFailures will be escalated to parent. + +### Par transputers. + + 'Par' is a group of transputers running in parallel. Par transputer can be created with the help of plus operator: + +~~~ scala +val par = (t1 + t1 + t3) +par.start() +~~~ + + When one from `t1`, `t2`, ... is stopped or failed, then all other members of `par` are stopped. After this `par` can be restarted according to current recovery policy. + + +### Replication + + Replicated transputer is a set of identical transputers t_{i}, running in parallel. It can be created with `gopherApi.replicate` call. Next code fragment: + +~~~ scala +val r = gopherApi.replicate[MyTransputer](10) +~~~ + + will produce ten copies of MyTransputer (`r` will be a container transputer for them). Ports of all replicated internal transputers will be shared with ports of the container. (I.e. if we will write something to input port then it will be read by one of the replicas; if one of the replicas will write something to out port, this will be visible in out port of the container.) + + Mapping from a container to replica port can be changed from sharing to other approaches, like duplicating or distributing, via applying port transformations. + + For example, next code fragment: + +~~~ scala +r.inA.duplicate() + .inB.distribute( _.hashCode ) +~~~ + + will set port `inA` be duplicated in replicas (i.e. message, send to container port `inA` will be received by each instance) and messages from `inB` will be distributed by hashcode: i.e. messages with the same hashcode will be directed to the same replica. Such behavior is useful when we keep in replicated transputer some state information about messages. + + Stopping and recovering of replicated transformer is the same as in `par` (i.e. stopping/failing of one instance will cause stopping/failing of container) + + Also note, that we can receive a sequence of replicated instances with the help of `ReplicateTransformer.replicated` method. + +## Unsugared interfaces + + It is worth to know that exists gopher API without macro-based syntax sugar. + +~~~ scala +( + new ForeverSelectorBuilder(gopherApi) + .reading(ch1){ x => something-x } + .writing(ch2,y){ y => something-y } + .idle(something idle).go +) +~~~ + + can be used instead of appropriative macro-based call. + + Moreover, for tricky things exists even low-level interface, which can combine computations by adding to functional interfaces, similar to continuations: + +~~~ scala +{ + val selector = new Selector[Unit](gopherApi) + selector.addReader(ch1, cont=>Some{ in => something-x + Future successful cont + } + ) + selector.addWriter(ch2, cont=>Some{(y,{something y; + Future successful cont + })}) + selector.addIdle(cont => {..do-something-when-idle; Future successful cont}) +} +~~~ + + Please, consult with source code for details. + + +## Additional Informatiom + ---------------------- + +* API reference: http://rssh.github.io/scala-gopher/api/index.html#package +* source code: https://github.com/rssh/scala-gopher +* presentations: + * Odessa Java/Scala Labs; Kiev Scala Meetup: Oct. 2014: http://www.slideshare.net/rssh1/scala-gopher2014 + * Wix R&D meetup. Mart 2016: http://www.slideshare.net/rssh1/csp-scala-wixmeetup2016 + * Scala Symposium. Oct. 2016. Amsterdam. http://www.slideshare.net/rssh1/scalagopher-cspstyle-programming-techniques-with-idiomatic-scala +* techreport: https://arxiv.org/abs/1611.00602 + + + Some related links: + +* [Communicating Sequential Processes book by Tony Hoare](http://www.usingcsp.com) +* [brief history of CSP in Bell-labs](http://swtch.com/~rsc/thread/) +* [introduction article about go defer/recover](http://blog.golang.org/defer-panic-and-recover) + diff --git a/0.99.x/build.sbt b/0.99.x/build.sbt new file mode 100644 index 00000000..b03da841 --- /dev/null +++ b/0.99.x/build.sbt @@ -0,0 +1,71 @@ + +name:="scala-gopher" + +organization:="com.github.rssh" + +scalaVersion := "2.13.3" +//crossScalaVersions := Seq("2.12.7") + +resolvers += Resolver.sonatypeRepo("snapshots") + +resolvers += "Typesafe Repository" at "https://repo.typesafe.com/typesafe/releases/" + +scalacOptions ++= Seq("-unchecked","-deprecation", "-feature", "-Xasync", + /* , "-Ymacro-debug-lite" */ + /* , "-Ydebug" , "-Ylog:lambdalift" */ + ) + +libraryDependencies += scalaVersion( "org.scala-lang" % "scala-reflect" % _ ).value + +libraryDependencies += "org.scala-lang.modules" %% "scala-async" % "0.10.0" + +libraryDependencies += "org.scalatest" %% "scalatest" % "3.0.8" % "test" + +libraryDependencies += "com.typesafe.akka" %% "akka-actor" % "2.6.8" + +//testOptions in Test += Tests.Argument(TestFrameworks.ScalaTest, "-n", "Now") +fork in Test := true +//javaOptions in Test += s"""-javaagent:${System.getProperty("user.home")}/.ivy2/local/com.github.rssh/trackedfuture_2.11/0.3/jars/trackedfuture_2.11-assembly.jar""" + +version:="0.99.16-SNAPSHOT" + +credentials += Credentials(Path.userHome / ".sbt" / "sonatype_credentials") + +publishMavenStyle := true + +publishTo := version { (v: String) => + val nexus = "https://oss.sonatype.org/" + if (v.trim.endsWith("SNAPSHOT")) + Some("snapshots" at nexus + "content/repositories/snapshots") + else + Some("releases" at nexus + "service/local/staging/deploy/maven2") +} .value + + +publishArtifact in Test := false + +pomIncludeRepository := { _ => false } + +pomExtra := ( + http://rssh.github.com/scala-gopher + + + Apache 2 + http://www.apache.org/licenses/LICENSE-2.0 + repo + + + + git@github.com:rssh/scala-gopher.git + scm:git:git@github.com:rssh/scala-gopher.git + + + + rssh + Ruslan Shevchenko + rssh.github.com + + +) + + diff --git a/0.99.x/project/plugins.sbt b/0.99.x/project/plugins.sbt new file mode 100644 index 00000000..5de36b3f --- /dev/null +++ b/0.99.x/project/plugins.sbt @@ -0,0 +1,3 @@ +//addSbtPlugin("com.typesafe.sbt" % "sbt-pgp" % "0.8.1") +addSbtPlugin("com.jsuereth" % "sbt-pgp" % "1.1.1") +addSbtPlugin("org.xerial.sbt" % "sbt-sonatype" % "2.5") diff --git a/src/main/scala/gopher/ChannelClosedException.scala b/0.99.x/src/main/scala/gopher/ChannelClosedException.scala similarity index 100% rename from src/main/scala/gopher/ChannelClosedException.scala rename to 0.99.x/src/main/scala/gopher/ChannelClosedException.scala diff --git a/src/main/scala/gopher/Defers.scala b/0.99.x/src/main/scala/gopher/Defers.scala similarity index 100% rename from src/main/scala/gopher/Defers.scala rename to 0.99.x/src/main/scala/gopher/Defers.scala diff --git a/src/main/scala/gopher/FlowTermination.scala b/0.99.x/src/main/scala/gopher/FlowTermination.scala similarity index 100% rename from src/main/scala/gopher/FlowTermination.scala rename to 0.99.x/src/main/scala/gopher/FlowTermination.scala diff --git a/src/main/scala/gopher/Gopher.scala b/0.99.x/src/main/scala/gopher/Gopher.scala similarity index 100% rename from src/main/scala/gopher/Gopher.scala rename to 0.99.x/src/main/scala/gopher/Gopher.scala diff --git a/src/main/scala/gopher/GopherAPI.scala b/0.99.x/src/main/scala/gopher/GopherAPI.scala similarity index 100% rename from src/main/scala/gopher/GopherAPI.scala rename to 0.99.x/src/main/scala/gopher/GopherAPI.scala diff --git a/src/main/scala/gopher/ThreadingPolicy.scala b/0.99.x/src/main/scala/gopher/ThreadingPolicy.scala similarity index 100% rename from src/main/scala/gopher/ThreadingPolicy.scala rename to 0.99.x/src/main/scala/gopher/ThreadingPolicy.scala diff --git a/src/main/scala/gopher/Time.scala b/0.99.x/src/main/scala/gopher/Time.scala similarity index 100% rename from src/main/scala/gopher/Time.scala rename to 0.99.x/src/main/scala/gopher/Time.scala diff --git a/src/main/scala/gopher/Transputer.scala b/0.99.x/src/main/scala/gopher/Transputer.scala similarity index 100% rename from src/main/scala/gopher/Transputer.scala rename to 0.99.x/src/main/scala/gopher/Transputer.scala diff --git a/src/main/scala/gopher/channels/ActorBackedChannel.scala b/0.99.x/src/main/scala/gopher/channels/ActorBackedChannel.scala similarity index 100% rename from src/main/scala/gopher/channels/ActorBackedChannel.scala rename to 0.99.x/src/main/scala/gopher/channels/ActorBackedChannel.scala diff --git a/src/main/scala/gopher/channels/BaseBufferedChannelActor.scala b/0.99.x/src/main/scala/gopher/channels/BaseBufferedChannelActor.scala similarity index 100% rename from src/main/scala/gopher/channels/BaseBufferedChannelActor.scala rename to 0.99.x/src/main/scala/gopher/channels/BaseBufferedChannelActor.scala diff --git a/src/main/scala/gopher/channels/BufferedChannelActor.scala b/0.99.x/src/main/scala/gopher/channels/BufferedChannelActor.scala similarity index 100% rename from src/main/scala/gopher/channels/BufferedChannelActor.scala rename to 0.99.x/src/main/scala/gopher/channels/BufferedChannelActor.scala diff --git a/src/main/scala/gopher/channels/Channel.scala b/0.99.x/src/main/scala/gopher/channels/Channel.scala similarity index 100% rename from src/main/scala/gopher/channels/Channel.scala rename to 0.99.x/src/main/scala/gopher/channels/Channel.scala diff --git a/src/main/scala/gopher/channels/ChannelActor.scala b/0.99.x/src/main/scala/gopher/channels/ChannelActor.scala similarity index 100% rename from src/main/scala/gopher/channels/ChannelActor.scala rename to 0.99.x/src/main/scala/gopher/channels/ChannelActor.scala diff --git a/src/main/scala/gopher/channels/ChannelActorMessage.scala b/0.99.x/src/main/scala/gopher/channels/ChannelActorMessage.scala similarity index 100% rename from src/main/scala/gopher/channels/ChannelActorMessage.scala rename to 0.99.x/src/main/scala/gopher/channels/ChannelActorMessage.scala diff --git a/src/main/scala/gopher/channels/ChannelProcessor.scala b/0.99.x/src/main/scala/gopher/channels/ChannelProcessor.scala similarity index 100% rename from src/main/scala/gopher/channels/ChannelProcessor.scala rename to 0.99.x/src/main/scala/gopher/channels/ChannelProcessor.scala diff --git a/src/main/scala/gopher/channels/ChannelSupervisor.scala b/0.99.x/src/main/scala/gopher/channels/ChannelSupervisor.scala similarity index 100% rename from src/main/scala/gopher/channels/ChannelSupervisor.scala rename to 0.99.x/src/main/scala/gopher/channels/ChannelSupervisor.scala diff --git a/src/main/scala/gopher/channels/CloseableInput.scala b/0.99.x/src/main/scala/gopher/channels/CloseableInput.scala similarity index 100% rename from src/main/scala/gopher/channels/CloseableInput.scala rename to 0.99.x/src/main/scala/gopher/channels/CloseableInput.scala diff --git a/src/main/scala/gopher/channels/Continuated.scala b/0.99.x/src/main/scala/gopher/channels/Continuated.scala similarity index 100% rename from src/main/scala/gopher/channels/Continuated.scala rename to 0.99.x/src/main/scala/gopher/channels/Continuated.scala diff --git a/src/main/scala/gopher/channels/CurrentFlowTermination.scala b/0.99.x/src/main/scala/gopher/channels/CurrentFlowTermination.scala similarity index 100% rename from src/main/scala/gopher/channels/CurrentFlowTermination.scala rename to 0.99.x/src/main/scala/gopher/channels/CurrentFlowTermination.scala diff --git a/src/main/scala/gopher/channels/DoneProvider.scala b/0.99.x/src/main/scala/gopher/channels/DoneProvider.scala similarity index 100% rename from src/main/scala/gopher/channels/DoneProvider.scala rename to 0.99.x/src/main/scala/gopher/channels/DoneProvider.scala diff --git a/src/main/scala/gopher/channels/DuppedInput.scala b/0.99.x/src/main/scala/gopher/channels/DuppedInput.scala similarity index 100% rename from src/main/scala/gopher/channels/DuppedInput.scala rename to 0.99.x/src/main/scala/gopher/channels/DuppedInput.scala diff --git a/src/main/scala/gopher/channels/EffectedChannel.scala b/0.99.x/src/main/scala/gopher/channels/EffectedChannel.scala similarity index 100% rename from src/main/scala/gopher/channels/EffectedChannel.scala rename to 0.99.x/src/main/scala/gopher/channels/EffectedChannel.scala diff --git a/src/main/scala/gopher/channels/EffectedInput.scala b/0.99.x/src/main/scala/gopher/channels/EffectedInput.scala similarity index 100% rename from src/main/scala/gopher/channels/EffectedInput.scala rename to 0.99.x/src/main/scala/gopher/channels/EffectedInput.scala diff --git a/src/main/scala/gopher/channels/EffectedOutput.scala b/0.99.x/src/main/scala/gopher/channels/EffectedOutput.scala similarity index 100% rename from src/main/scala/gopher/channels/EffectedOutput.scala rename to 0.99.x/src/main/scala/gopher/channels/EffectedOutput.scala diff --git a/src/main/scala/gopher/channels/ExpireChannel.scala b/0.99.x/src/main/scala/gopher/channels/ExpireChannel.scala similarity index 100% rename from src/main/scala/gopher/channels/ExpireChannel.scala rename to 0.99.x/src/main/scala/gopher/channels/ExpireChannel.scala diff --git a/src/main/scala/gopher/channels/FoldSelectorBuilder.scala b/0.99.x/src/main/scala/gopher/channels/FoldSelectorBuilder.scala similarity index 100% rename from src/main/scala/gopher/channels/FoldSelectorBuilder.scala rename to 0.99.x/src/main/scala/gopher/channels/FoldSelectorBuilder.scala diff --git a/src/main/scala/gopher/channels/ForeverSelectorBuilder.scala b/0.99.x/src/main/scala/gopher/channels/ForeverSelectorBuilder.scala similarity index 100% rename from src/main/scala/gopher/channels/ForeverSelectorBuilder.scala rename to 0.99.x/src/main/scala/gopher/channels/ForeverSelectorBuilder.scala diff --git a/src/main/scala/gopher/channels/FutureInput.scala b/0.99.x/src/main/scala/gopher/channels/FutureInput.scala similarity index 100% rename from src/main/scala/gopher/channels/FutureInput.scala rename to 0.99.x/src/main/scala/gopher/channels/FutureInput.scala diff --git a/src/main/scala/gopher/channels/GopherAPIProvider.scala b/0.99.x/src/main/scala/gopher/channels/GopherAPIProvider.scala similarity index 100% rename from src/main/scala/gopher/channels/GopherAPIProvider.scala rename to 0.99.x/src/main/scala/gopher/channels/GopherAPIProvider.scala diff --git a/src/main/scala/gopher/channels/GrowingBufferedChannel.scala b/0.99.x/src/main/scala/gopher/channels/GrowingBufferedChannel.scala similarity index 100% rename from src/main/scala/gopher/channels/GrowingBufferedChannel.scala rename to 0.99.x/src/main/scala/gopher/channels/GrowingBufferedChannel.scala diff --git a/src/main/scala/gopher/channels/Input.scala b/0.99.x/src/main/scala/gopher/channels/Input.scala similarity index 100% rename from src/main/scala/gopher/channels/Input.scala rename to 0.99.x/src/main/scala/gopher/channels/Input.scala diff --git a/src/main/scala/gopher/channels/InputOutput.scala b/0.99.x/src/main/scala/gopher/channels/InputOutput.scala similarity index 100% rename from src/main/scala/gopher/channels/InputOutput.scala rename to 0.99.x/src/main/scala/gopher/channels/InputOutput.scala diff --git a/src/main/scala/gopher/channels/InputSelectorBuilder.scala b/0.99.x/src/main/scala/gopher/channels/InputSelectorBuilder.scala similarity index 100% rename from src/main/scala/gopher/channels/InputSelectorBuilder.scala rename to 0.99.x/src/main/scala/gopher/channels/InputSelectorBuilder.scala diff --git a/src/main/scala/gopher/channels/InputWithTimeouts.scala b/0.99.x/src/main/scala/gopher/channels/InputWithTimeouts.scala similarity index 100% rename from src/main/scala/gopher/channels/InputWithTimeouts.scala rename to 0.99.x/src/main/scala/gopher/channels/InputWithTimeouts.scala diff --git a/src/main/scala/gopher/channels/LazyChannel.scala b/0.99.x/src/main/scala/gopher/channels/LazyChannel.scala similarity index 100% rename from src/main/scala/gopher/channels/LazyChannel.scala rename to 0.99.x/src/main/scala/gopher/channels/LazyChannel.scala diff --git a/src/main/scala/gopher/channels/OnceSelectorBuilder.scala b/0.99.x/src/main/scala/gopher/channels/OnceSelectorBuilder.scala similarity index 100% rename from src/main/scala/gopher/channels/OnceSelectorBuilder.scala rename to 0.99.x/src/main/scala/gopher/channels/OnceSelectorBuilder.scala diff --git a/src/main/scala/gopher/channels/OneTimeChannel.scala b/0.99.x/src/main/scala/gopher/channels/OneTimeChannel.scala similarity index 100% rename from src/main/scala/gopher/channels/OneTimeChannel.scala rename to 0.99.x/src/main/scala/gopher/channels/OneTimeChannel.scala diff --git a/src/main/scala/gopher/channels/OrInput.scala b/0.99.x/src/main/scala/gopher/channels/OrInput.scala similarity index 100% rename from src/main/scala/gopher/channels/OrInput.scala rename to 0.99.x/src/main/scala/gopher/channels/OrInput.scala diff --git a/src/main/scala/gopher/channels/Output.scala b/0.99.x/src/main/scala/gopher/channels/Output.scala similarity index 100% rename from src/main/scala/gopher/channels/Output.scala rename to 0.99.x/src/main/scala/gopher/channels/Output.scala diff --git a/src/main/scala/gopher/channels/OutputWithTimeouts.scala b/0.99.x/src/main/scala/gopher/channels/OutputWithTimeouts.scala similarity index 100% rename from src/main/scala/gopher/channels/OutputWithTimeouts.scala rename to 0.99.x/src/main/scala/gopher/channels/OutputWithTimeouts.scala diff --git a/src/main/scala/gopher/channels/PromiseFlowTermination.scala b/0.99.x/src/main/scala/gopher/channels/PromiseFlowTermination.scala similarity index 100% rename from src/main/scala/gopher/channels/PromiseFlowTermination.scala rename to 0.99.x/src/main/scala/gopher/channels/PromiseFlowTermination.scala diff --git a/src/main/scala/gopher/channels/Selector.scala b/0.99.x/src/main/scala/gopher/channels/Selector.scala similarity index 100% rename from src/main/scala/gopher/channels/Selector.scala rename to 0.99.x/src/main/scala/gopher/channels/Selector.scala diff --git a/src/main/scala/gopher/channels/SelectorArguments.scala b/0.99.x/src/main/scala/gopher/channels/SelectorArguments.scala similarity index 100% rename from src/main/scala/gopher/channels/SelectorArguments.scala rename to 0.99.x/src/main/scala/gopher/channels/SelectorArguments.scala diff --git a/src/main/scala/gopher/channels/SelectorBuilder.scala b/0.99.x/src/main/scala/gopher/channels/SelectorBuilder.scala similarity index 100% rename from src/main/scala/gopher/channels/SelectorBuilder.scala rename to 0.99.x/src/main/scala/gopher/channels/SelectorBuilder.scala diff --git a/src/main/scala/gopher/channels/SelectorFactory.scala b/0.99.x/src/main/scala/gopher/channels/SelectorFactory.scala similarity index 100% rename from src/main/scala/gopher/channels/SelectorFactory.scala rename to 0.99.x/src/main/scala/gopher/channels/SelectorFactory.scala diff --git a/src/main/scala/gopher/channels/UnbufferedChannelActor.scala b/0.99.x/src/main/scala/gopher/channels/UnbufferedChannelActor.scala similarity index 100% rename from src/main/scala/gopher/channels/UnbufferedChannelActor.scala rename to 0.99.x/src/main/scala/gopher/channels/UnbufferedChannelActor.scala diff --git a/src/main/scala/gopher/channels/ZippedInput.scala b/0.99.x/src/main/scala/gopher/channels/ZippedInput.scala similarity index 100% rename from src/main/scala/gopher/channels/ZippedInput.scala rename to 0.99.x/src/main/scala/gopher/channels/ZippedInput.scala diff --git a/src/main/scala/gopher/channels/package.scala b/0.99.x/src/main/scala/gopher/channels/package.scala similarity index 100% rename from src/main/scala/gopher/channels/package.scala rename to 0.99.x/src/main/scala/gopher/channels/package.scala diff --git a/src/main/scala/gopher/goasync/AsyncApply.scala b/0.99.x/src/main/scala/gopher/goasync/AsyncApply.scala similarity index 100% rename from src/main/scala/gopher/goasync/AsyncApply.scala rename to 0.99.x/src/main/scala/gopher/goasync/AsyncApply.scala diff --git a/src/main/scala/gopher/goasync/AsyncIterable.scala b/0.99.x/src/main/scala/gopher/goasync/AsyncIterable.scala similarity index 100% rename from src/main/scala/gopher/goasync/AsyncIterable.scala rename to 0.99.x/src/main/scala/gopher/goasync/AsyncIterable.scala diff --git a/src/main/scala/gopher/goasync/AsyncOption.scala b/0.99.x/src/main/scala/gopher/goasync/AsyncOption.scala similarity index 100% rename from src/main/scala/gopher/goasync/AsyncOption.scala rename to 0.99.x/src/main/scala/gopher/goasync/AsyncOption.scala diff --git a/src/main/scala/gopher/goasync/AsyncWrapper.scala b/0.99.x/src/main/scala/gopher/goasync/AsyncWrapper.scala similarity index 100% rename from src/main/scala/gopher/goasync/AsyncWrapper.scala rename to 0.99.x/src/main/scala/gopher/goasync/AsyncWrapper.scala diff --git a/src/main/scala/gopher/goasync/GoAsync.scala b/0.99.x/src/main/scala/gopher/goasync/GoAsync.scala similarity index 100% rename from src/main/scala/gopher/goasync/GoAsync.scala rename to 0.99.x/src/main/scala/gopher/goasync/GoAsync.scala diff --git a/src/main/scala/gopher/package.scala b/0.99.x/src/main/scala/gopher/package.scala similarity index 100% rename from src/main/scala/gopher/package.scala rename to 0.99.x/src/main/scala/gopher/package.scala diff --git a/src/main/scala/gopher/transputers/ReplicateTransputer.scala b/0.99.x/src/main/scala/gopher/transputers/ReplicateTransputer.scala similarity index 100% rename from src/main/scala/gopher/transputers/ReplicateTransputer.scala rename to 0.99.x/src/main/scala/gopher/transputers/ReplicateTransputer.scala diff --git a/src/main/scala/gopher/transputers/TransputerSupervisor.scala b/0.99.x/src/main/scala/gopher/transputers/TransputerSupervisor.scala similarity index 100% rename from src/main/scala/gopher/transputers/TransputerSupervisor.scala rename to 0.99.x/src/main/scala/gopher/transputers/TransputerSupervisor.scala diff --git a/src/main/scala/gopher/transputers/package.scala b/0.99.x/src/main/scala/gopher/transputers/package.scala similarity index 100% rename from src/main/scala/gopher/transputers/package.scala rename to 0.99.x/src/main/scala/gopher/transputers/package.scala diff --git a/src/main/scala/gopher/util/ASTUtilImpl.scala b/0.99.x/src/main/scala/gopher/util/ASTUtilImpl.scala similarity index 100% rename from src/main/scala/gopher/util/ASTUtilImpl.scala rename to 0.99.x/src/main/scala/gopher/util/ASTUtilImpl.scala diff --git a/src/main/scala/gopher/util/Effected.scala b/0.99.x/src/main/scala/gopher/util/Effected.scala similarity index 100% rename from src/main/scala/gopher/util/Effected.scala rename to 0.99.x/src/main/scala/gopher/util/Effected.scala diff --git a/src/main/scala/gopher/util/IntIndexedReverse.scala b/0.99.x/src/main/scala/gopher/util/IntIndexedReverse.scala similarity index 100% rename from src/main/scala/gopher/util/IntIndexedReverse.scala rename to 0.99.x/src/main/scala/gopher/util/IntIndexedReverse.scala diff --git a/src/main/scala/gopher/util/MacroUtil.scala b/0.99.x/src/main/scala/gopher/util/MacroUtil.scala similarity index 100% rename from src/main/scala/gopher/util/MacroUtil.scala rename to 0.99.x/src/main/scala/gopher/util/MacroUtil.scala diff --git a/src/main/scala/gopher/util/ReflectUtil.scala b/0.99.x/src/main/scala/gopher/util/ReflectUtil.scala similarity index 100% rename from src/main/scala/gopher/util/ReflectUtil.scala rename to 0.99.x/src/main/scala/gopher/util/ReflectUtil.scala diff --git a/src/test/resources/application.conf b/0.99.x/src/test/resources/application.conf similarity index 100% rename from src/test/resources/application.conf rename to 0.99.x/src/test/resources/application.conf diff --git a/src/test/scala/example/BetterSieveSuite.scala b/0.99.x/src/test/scala/example/BetterSieveSuite.scala similarity index 100% rename from src/test/scala/example/BetterSieveSuite.scala rename to 0.99.x/src/test/scala/example/BetterSieveSuite.scala diff --git a/src/test/scala/example/Bingo.scala b/0.99.x/src/test/scala/example/Bingo.scala similarity index 100% rename from src/test/scala/example/Bingo.scala rename to 0.99.x/src/test/scala/example/Bingo.scala diff --git a/src/test/scala/example/BroadcasterSuite.scala b/0.99.x/src/test/scala/example/BroadcasterSuite.scala similarity index 100% rename from src/test/scala/example/BroadcasterSuite.scala rename to 0.99.x/src/test/scala/example/BroadcasterSuite.scala diff --git a/src/test/scala/example/CopyFile.scala b/0.99.x/src/test/scala/example/CopyFile.scala similarity index 100% rename from src/test/scala/example/CopyFile.scala rename to 0.99.x/src/test/scala/example/CopyFile.scala diff --git a/src/test/scala/example/FibonaccyAsyncSuite.scala b/0.99.x/src/test/scala/example/FibonaccyAsyncSuite.scala similarity index 100% rename from src/test/scala/example/FibonaccyAsyncSuite.scala rename to 0.99.x/src/test/scala/example/FibonaccyAsyncSuite.scala diff --git a/src/test/scala/example/FibonaccyAsyncUnsugaredSuite.scala b/0.99.x/src/test/scala/example/FibonaccyAsyncUnsugaredSuite.scala similarity index 100% rename from src/test/scala/example/FibonaccyAsyncUnsugaredSuite.scala rename to 0.99.x/src/test/scala/example/FibonaccyAsyncUnsugaredSuite.scala diff --git a/src/test/scala/example/FibonaccyFoldSuite.scala b/0.99.x/src/test/scala/example/FibonaccyFoldSuite.scala similarity index 100% rename from src/test/scala/example/FibonaccyFoldSuite.scala rename to 0.99.x/src/test/scala/example/FibonaccyFoldSuite.scala diff --git a/src/test/scala/example/FibonaccySuite.scala b/0.99.x/src/test/scala/example/FibonaccySuite.scala similarity index 100% rename from src/test/scala/example/FibonaccySuite.scala rename to 0.99.x/src/test/scala/example/FibonaccySuite.scala diff --git a/src/test/scala/example/SieveSuite.scala b/0.99.x/src/test/scala/example/SieveSuite.scala similarity index 100% rename from src/test/scala/example/SieveSuite.scala rename to 0.99.x/src/test/scala/example/SieveSuite.scala diff --git a/src/test/scala/gopher/channels/AsyncSelectSuite.scala b/0.99.x/src/test/scala/gopher/channels/AsyncSelectSuite.scala similarity index 100% rename from src/test/scala/gopher/channels/AsyncSelectSuite.scala rename to 0.99.x/src/test/scala/gopher/channels/AsyncSelectSuite.scala diff --git a/src/test/scala/gopher/channels/ChannelCleanupSuite.scala b/0.99.x/src/test/scala/gopher/channels/ChannelCleanupSuite.scala similarity index 100% rename from src/test/scala/gopher/channels/ChannelCleanupSuite.scala rename to 0.99.x/src/test/scala/gopher/channels/ChannelCleanupSuite.scala diff --git a/src/test/scala/gopher/channels/ChannelCloseSuite.scala b/0.99.x/src/test/scala/gopher/channels/ChannelCloseSuite.scala similarity index 100% rename from src/test/scala/gopher/channels/ChannelCloseSuite.scala rename to 0.99.x/src/test/scala/gopher/channels/ChannelCloseSuite.scala diff --git a/src/test/scala/gopher/channels/CommonTestObjects.scala b/0.99.x/src/test/scala/gopher/channels/CommonTestObjects.scala similarity index 100% rename from src/test/scala/gopher/channels/CommonTestObjects.scala rename to 0.99.x/src/test/scala/gopher/channels/CommonTestObjects.scala diff --git a/src/test/scala/gopher/channels/DuppedChannelsSuite.scala b/0.99.x/src/test/scala/gopher/channels/DuppedChannelsSuite.scala similarity index 100% rename from src/test/scala/gopher/channels/DuppedChannelsSuite.scala rename to 0.99.x/src/test/scala/gopher/channels/DuppedChannelsSuite.scala diff --git a/src/test/scala/gopher/channels/ExpireChannelSuite.scala b/0.99.x/src/test/scala/gopher/channels/ExpireChannelSuite.scala similarity index 100% rename from src/test/scala/gopher/channels/ExpireChannelSuite.scala rename to 0.99.x/src/test/scala/gopher/channels/ExpireChannelSuite.scala diff --git a/src/test/scala/gopher/channels/FlowTerminationSuite.scala b/0.99.x/src/test/scala/gopher/channels/FlowTerminationSuite.scala similarity index 100% rename from src/test/scala/gopher/channels/FlowTerminationSuite.scala rename to 0.99.x/src/test/scala/gopher/channels/FlowTerminationSuite.scala diff --git a/src/test/scala/gopher/channels/FoldSelectSuite.scala b/0.99.x/src/test/scala/gopher/channels/FoldSelectSuite.scala similarity index 100% rename from src/test/scala/gopher/channels/FoldSelectSuite.scala rename to 0.99.x/src/test/scala/gopher/channels/FoldSelectSuite.scala diff --git a/src/test/scala/gopher/channels/IOComposeSuite.scala b/0.99.x/src/test/scala/gopher/channels/IOComposeSuite.scala similarity index 100% rename from src/test/scala/gopher/channels/IOComposeSuite.scala rename to 0.99.x/src/test/scala/gopher/channels/IOComposeSuite.scala diff --git a/src/test/scala/gopher/channels/IOTimeoutsSuite.scala b/0.99.x/src/test/scala/gopher/channels/IOTimeoutsSuite.scala similarity index 100% rename from src/test/scala/gopher/channels/IOTimeoutsSuite.scala rename to 0.99.x/src/test/scala/gopher/channels/IOTimeoutsSuite.scala diff --git a/src/test/scala/gopher/channels/InputOpsSuite.scala b/0.99.x/src/test/scala/gopher/channels/InputOpsSuite.scala similarity index 100% rename from src/test/scala/gopher/channels/InputOpsSuite.scala rename to 0.99.x/src/test/scala/gopher/channels/InputOpsSuite.scala diff --git a/src/test/scala/gopher/channels/MacroSelectSuite.scala b/0.99.x/src/test/scala/gopher/channels/MacroSelectSuite.scala similarity index 100% rename from src/test/scala/gopher/channels/MacroSelectSuite.scala rename to 0.99.x/src/test/scala/gopher/channels/MacroSelectSuite.scala diff --git a/src/test/scala/gopher/channels/ReadCoroutinesSuite.scala b/0.99.x/src/test/scala/gopher/channels/ReadCoroutinesSuite.scala similarity index 100% rename from src/test/scala/gopher/channels/ReadCoroutinesSuite.scala rename to 0.99.x/src/test/scala/gopher/channels/ReadCoroutinesSuite.scala diff --git a/src/test/scala/gopher/channels/SchedulerStartupTest.scala b/0.99.x/src/test/scala/gopher/channels/SchedulerStartupTest.scala similarity index 100% rename from src/test/scala/gopher/channels/SchedulerStartupTest.scala rename to 0.99.x/src/test/scala/gopher/channels/SchedulerStartupTest.scala diff --git a/src/test/scala/gopher/channels/SelectErrorSuite.scala b/0.99.x/src/test/scala/gopher/channels/SelectErrorSuite.scala similarity index 100% rename from src/test/scala/gopher/channels/SelectErrorSuite.scala rename to 0.99.x/src/test/scala/gopher/channels/SelectErrorSuite.scala diff --git a/src/test/scala/gopher/channels/SelectSuite.scala b/0.99.x/src/test/scala/gopher/channels/SelectSuite.scala similarity index 100% rename from src/test/scala/gopher/channels/SelectSuite.scala rename to 0.99.x/src/test/scala/gopher/channels/SelectSuite.scala diff --git a/src/test/scala/gopher/channels/SelectTimeoutSuite.scala b/0.99.x/src/test/scala/gopher/channels/SelectTimeoutSuite.scala similarity index 100% rename from src/test/scala/gopher/channels/SelectTimeoutSuite.scala rename to 0.99.x/src/test/scala/gopher/channels/SelectTimeoutSuite.scala diff --git a/src/test/scala/gopher/channels/UnbufferedSelectSuite.scala b/0.99.x/src/test/scala/gopher/channels/UnbufferedSelectSuite.scala similarity index 100% rename from src/test/scala/gopher/channels/UnbufferedSelectSuite.scala rename to 0.99.x/src/test/scala/gopher/channels/UnbufferedSelectSuite.scala diff --git a/src/test/scala/gopher/hofasyn/FibbonacyAsyncLoopSuite.scala b/0.99.x/src/test/scala/gopher/hofasyn/FibbonacyAsyncLoopSuite.scala similarity index 100% rename from src/test/scala/gopher/hofasyn/FibbonacyAsyncLoopSuite.scala rename to 0.99.x/src/test/scala/gopher/hofasyn/FibbonacyAsyncLoopSuite.scala diff --git a/src/test/scala/gopher/hofasyn/HofAsyncSuite.scala b/0.99.x/src/test/scala/gopher/hofasyn/HofAsyncSuite.scala similarity index 100% rename from src/test/scala/gopher/hofasyn/HofAsyncSuite.scala rename to 0.99.x/src/test/scala/gopher/hofasyn/HofAsyncSuite.scala diff --git a/src/test/scala/gopher/internal/FoldParseSuite.scala b/0.99.x/src/test/scala/gopher/internal/FoldParseSuite.scala similarity index 100% rename from src/test/scala/gopher/internal/FoldParseSuite.scala rename to 0.99.x/src/test/scala/gopher/internal/FoldParseSuite.scala diff --git a/src/test/scala/gopher/scope/DefersSuite.scala b/0.99.x/src/test/scala/gopher/scope/DefersSuite.scala similarity index 100% rename from src/test/scala/gopher/scope/DefersSuite.scala rename to 0.99.x/src/test/scala/gopher/scope/DefersSuite.scala diff --git a/src/test/scala/gopher/scope/GoWithDeferSuite.scala b/0.99.x/src/test/scala/gopher/scope/GoWithDeferSuite.scala similarity index 100% rename from src/test/scala/gopher/scope/GoWithDeferSuite.scala rename to 0.99.x/src/test/scala/gopher/scope/GoWithDeferSuite.scala diff --git a/src/test/scala/gopher/scope/ScopeMacroSuite.scala b/0.99.x/src/test/scala/gopher/scope/ScopeMacroSuite.scala similarity index 100% rename from src/test/scala/gopher/scope/ScopeMacroSuite.scala rename to 0.99.x/src/test/scala/gopher/scope/ScopeMacroSuite.scala diff --git a/src/test/scala/gopher/scope/SimpleStatementSuite.scala b/0.99.x/src/test/scala/gopher/scope/SimpleStatementSuite.scala similarity index 100% rename from src/test/scala/gopher/scope/SimpleStatementSuite.scala rename to 0.99.x/src/test/scala/gopher/scope/SimpleStatementSuite.scala diff --git a/src/test/scala/gopher/tags/Gen.scala b/0.99.x/src/test/scala/gopher/tags/Gen.scala similarity index 100% rename from src/test/scala/gopher/tags/Gen.scala rename to 0.99.x/src/test/scala/gopher/tags/Gen.scala diff --git a/src/test/scala/gopher/tags/Now.scala b/0.99.x/src/test/scala/gopher/tags/Now.scala similarity index 100% rename from src/test/scala/gopher/tags/Now.scala rename to 0.99.x/src/test/scala/gopher/tags/Now.scala diff --git a/src/test/scala/gopher/time/TimeSuite.scala b/0.99.x/src/test/scala/gopher/time/TimeSuite.scala similarity index 100% rename from src/test/scala/gopher/time/TimeSuite.scala rename to 0.99.x/src/test/scala/gopher/time/TimeSuite.scala diff --git a/src/test/scala/gopher/transputers/ReplicateSuite.scala b/0.99.x/src/test/scala/gopher/transputers/ReplicateSuite.scala similarity index 100% rename from src/test/scala/gopher/transputers/ReplicateSuite.scala rename to 0.99.x/src/test/scala/gopher/transputers/ReplicateSuite.scala diff --git a/src/test/scala/gopher/transputers/TransputerRestartTest.scala b/0.99.x/src/test/scala/gopher/transputers/TransputerRestartTest.scala similarity index 100% rename from src/test/scala/gopher/transputers/TransputerRestartTest.scala rename to 0.99.x/src/test/scala/gopher/transputers/TransputerRestartTest.scala diff --git a/NOTICE b/NOTICE index e9a4d7e6..ab7bab67 100644 --- a/NOTICE +++ b/NOTICE @@ -1,5 +1,5 @@ Scala-Gopher. - Copyright 2013-2018 Ruslan Shevchenko + Copyright 2013-2020 Ruslan Shevchenko diff --git a/build.sbt b/build.sbt index b03da841..5c8f92d1 100644 --- a/build.sbt +++ b/build.sbt @@ -1,71 +1,38 @@ - -name:="scala-gopher" - -organization:="com.github.rssh" - -scalaVersion := "2.13.3" -//crossScalaVersions := Seq("2.12.7") - -resolvers += Resolver.sonatypeRepo("snapshots") - -resolvers += "Typesafe Repository" at "https://repo.typesafe.com/typesafe/releases/" - -scalacOptions ++= Seq("-unchecked","-deprecation", "-feature", "-Xasync", - /* , "-Ymacro-debug-lite" */ - /* , "-Ydebug" , "-Ylog:lambdalift" */ - ) - -libraryDependencies += scalaVersion( "org.scala-lang" % "scala-reflect" % _ ).value - -libraryDependencies += "org.scala-lang.modules" %% "scala-async" % "0.10.0" - -libraryDependencies += "org.scalatest" %% "scalatest" % "3.0.8" % "test" - -libraryDependencies += "com.typesafe.akka" %% "akka-actor" % "2.6.8" - -//testOptions in Test += Tests.Argument(TestFrameworks.ScalaTest, "-n", "Now") -fork in Test := true -//javaOptions in Test += s"""-javaagent:${System.getProperty("user.home")}/.ivy2/local/com.github.rssh/trackedfuture_2.11/0.3/jars/trackedfuture_2.11-assembly.jar""" - -version:="0.99.16-SNAPSHOT" - -credentials += Credentials(Path.userHome / ".sbt" / "sonatype_credentials") - -publishMavenStyle := true - -publishTo := version { (v: String) => - val nexus = "https://oss.sonatype.org/" - if (v.trim.endsWith("SNAPSHOT")) - Some("snapshots" at nexus + "content/repositories/snapshots") - else - Some("releases" at nexus + "service/local/staging/deploy/maven2") -} .value - - -publishArtifact in Test := false - -pomIncludeRepository := { _ => false } - -pomExtra := ( - http://rssh.github.com/scala-gopher - - - Apache 2 - http://www.apache.org/licenses/LICENSE-2.0 - repo - - - - git@github.com:rssh/scala-gopher.git - scm:git:git@github.com:rssh/scala-gopher.git - - - - rssh - Ruslan Shevchenko - rssh.github.com - - +//val dottyVersion = "3.0.0-M1-bin-20201022-b26dbc4-NIGHTLY" +val dottyVersion = "3.0.0-RC1-bin-SNAPSHOT" +//val dottyVersion = dottyLatestNightlyBuild.get + + +val sharedSettings = Seq( + version := "2.0.0-SNAPSHOT", + organization := "com.github.rssh", + scalaVersion := dottyVersion, + name := "scala-gopher", + //resolvers += "Local Ivy Repository" at "file://"+Path.userHome.absolutePath+"/.ivy2/local", + resolvers += "Local Ivy Repository" at "file://"+Path.userHome.absolutePath+"/.ivy2/local", + libraryDependencies += "com.github.rssh" %%% "dotty-cps-async" % "0.3.4-SNAPSHOT", ) +lazy val root = project + .in(file(".")) + .aggregate(gopher.js, gopher.jvm) + .settings( + Sphinx / sourceDirectory := baseDirectory.value / "docs", + git.remoteRepo := "git@github.com:rssh/dotty-cps-async.git", + publishArtifact := false, + ).enablePlugins(SphinxPlugin) + .enablePlugins(GhpagesPlugin) + + +lazy val gopher = crossProject(JSPlatform, JVMPlatform) + .in(file(".")) + .settings(sharedSettings) + .disablePlugins(SitePlugin) + .jvmSettings( + scalacOptions ++= Seq( "-unchecked" ), + libraryDependencies += "com.novocode" % "junit-interface" % "0.11" % "test", + ).jsSettings( + scalaJSUseMainModuleInitializer := true, + libraryDependencies += ("org.scala-js" %% "scalajs-junit-test-runtime" % "1.2.0" % Test).withDottyCompat(scalaVersion.value) + ) diff --git a/notes/0.99.1.markdown b/docs/history/notes/0.99.1.markdown similarity index 100% rename from notes/0.99.1.markdown rename to docs/history/notes/0.99.1.markdown diff --git a/notes/0.99.12.markdown b/docs/history/notes/0.99.12.markdown similarity index 100% rename from notes/0.99.12.markdown rename to docs/history/notes/0.99.12.markdown diff --git a/notes/0.99.2.markdown b/docs/history/notes/0.99.2.markdown similarity index 100% rename from notes/0.99.2.markdown rename to docs/history/notes/0.99.2.markdown diff --git a/notes/0.99.3.markdown b/docs/history/notes/0.99.3.markdown similarity index 100% rename from notes/0.99.3.markdown rename to docs/history/notes/0.99.3.markdown diff --git a/notes/0.99.4.markdown b/docs/history/notes/0.99.4.markdown similarity index 100% rename from notes/0.99.4.markdown rename to docs/history/notes/0.99.4.markdown diff --git a/notes/0.99.5.markdown b/docs/history/notes/0.99.5.markdown similarity index 100% rename from notes/0.99.5.markdown rename to docs/history/notes/0.99.5.markdown diff --git a/notes/0.99.6.markdown b/docs/history/notes/0.99.6.markdown similarity index 100% rename from notes/0.99.6.markdown rename to docs/history/notes/0.99.6.markdown diff --git a/notes/0.99.7.markdown b/docs/history/notes/0.99.7.markdown similarity index 100% rename from notes/0.99.7.markdown rename to docs/history/notes/0.99.7.markdown diff --git a/notes/0.99.8.markdown b/docs/history/notes/0.99.8.markdown similarity index 100% rename from notes/0.99.8.markdown rename to docs/history/notes/0.99.8.markdown diff --git a/notes/0.99.9.markdown b/docs/history/notes/0.99.9.markdown similarity index 100% rename from notes/0.99.9.markdown rename to docs/history/notes/0.99.9.markdown diff --git a/notes/about.markdown b/docs/history/notes/about.markdown similarity index 100% rename from notes/about.markdown rename to docs/history/notes/about.markdown diff --git a/notes/papers.markdown b/docs/history/notes/papers.markdown similarity index 100% rename from notes/papers.markdown rename to docs/history/notes/papers.markdown diff --git a/notes/techreport.pdf b/docs/history/notes/techreport.pdf similarity index 100% rename from notes/techreport.pdf rename to docs/history/notes/techreport.pdf diff --git a/notes/techreport.bib b/docs/history/techreport.bib similarity index 100% rename from notes/techreport.bib rename to docs/history/techreport.bib diff --git a/notes/techreport.tex b/docs/history/techreport.tex similarity index 100% rename from notes/techreport.tex rename to docs/history/techreport.tex diff --git a/jvm/src/main/scala/gopher/impl/MTUnbufferedChannel.scala b/jvm/src/main/scala/gopher/impl/MTUnbufferedChannel.scala new file mode 100644 index 00000000..818892e9 --- /dev/null +++ b/jvm/src/main/scala/gopher/impl/MTUnbufferedChannel.scala @@ -0,0 +1,86 @@ +package gopher.impl + +import cps._ +import gopher._ + +import java.lang.Runnable +import java.util.concurrent.Executor +import java.util.concurrent.ExecutorService +import java.util.concurrent.ConcurrentLinkedDeque +import java.util.concurrent.atomic.AtomicBoolean +import java.util.concurrent.atomic.AtomicReference +import scala.util.Try +import scala.util.Success +import scala.util.Failure + +class MTUnbufferedChannel[F[_]:CpsAsyncMonad,A <: AnyRef](controlExecutor: ExecutorService, taskExecutor: ExecutorService) extends Channel[F,A] with IOChannel[F,A,A] with OChannel[F,A] with IChannel[F,A]: + + private val readers = new ConcurrentLinkedDeque[Reader[A]]() + private val writers = new ConcurrentLinkedDeque[Writer[A]]() + private val ref = new AtomicReference[A|Null](null) + private val isClosed = new AtomicBoolean(false) + private val stepRunnable: Runnable = (()=>step()) + + def addReader(reader: Reader[A]): Unit = + if (reader.canExpire) then + readers.removeIf( _.isExpired ) + readers.add(reader) + controlExecutor.submit(stepRunnable) + + def addWriter(writer: Writer[A]): Unit = + if (writer.canExpire) then + writers.removeIf( _.isExpired ) + writers.add(writer) + controlExecutor.submit(stepRunnable) + + + + // called only from control executor + def step(): Unit = + var progress = true + while(progress) + progress = false + val a: A|Null = ref.get() + if !(a eq null) then + val reader = readers.poll() + if !(reader eq null) then + progress = true + reader.capture() match + case Some(f) => + if (ref.compareAndSet(a,null)) + taskExecutor.execute( ()=>f(Success(a.nn)) ) // TODO: what if f throws exception [?] + reader.markUsed() + else + // somebody from other thread stole our 'a', so need to try again + reader.markFree() + readers.addFirst(reader) + case None => + // Here should be next variant: + // this select group is other thread in other channel + // if this call will successfull - reader become expired. + // if not - we should not keep one here. (but if it's bloked - we can put one at the end of the + // tail to try next thing now) + // (when this is one reader - this will create something like spin-lock, when we will have no real progres here, + // but know, that some progress was in the other thread) + if (!reader.isExpired) + if (readers.isEmpty) Thread.`yield`() + readers.addLast(reader) + else + val writer = writers.poll() + if !(writer eq null) then + progress = true + writer.capture() match + case Some((a,f)) => + if (ref.compareAndSet(null,a)) + taskExecutor.execute( () => f(Success(())) ) + writer.markUsed() + else + // somebody fill our references. + writer.markFree() + writers.addFirst(writer) + case None => + if (!writer.isExpired) + if (writers.isEmpty) Thread.`yield`() + writers.addLast(writer) + + diff --git a/project/build.properties b/project/build.properties new file mode 100644 index 00000000..c19c768d --- /dev/null +++ b/project/build.properties @@ -0,0 +1 @@ +sbt.version=1.4.2 diff --git a/project/plugins.sbt b/project/plugins.sbt index 5de36b3f..dafc0603 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,3 +1,7 @@ -//addSbtPlugin("com.typesafe.sbt" % "sbt-pgp" % "0.8.1") -addSbtPlugin("com.jsuereth" % "sbt-pgp" % "1.1.1") -addSbtPlugin("org.xerial.sbt" % "sbt-sonatype" % "2.5") +addSbtPlugin("ch.epfl.lamp" % "sbt-dotty" % "0.4.6") +addSbtPlugin("com.jsuereth" % "sbt-pgp" % "2.0.1") +addSbtPlugin("com.typesafe.sbt" % "sbt-site" % "1.4.0") +addSbtPlugin("com.typesafe.sbt" % "sbt-ghpages" % "0.6.3") +addSbtPlugin("org.portable-scala" % "sbt-scalajs-crossproject" % "1.0.0") +addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.3.0") + diff --git a/shared/src/main/scala/gopher/Channel.scala b/shared/src/main/scala/gopher/Channel.scala new file mode 100644 index 00000000..b4c4fae9 --- /dev/null +++ b/shared/src/main/scala/gopher/Channel.scala @@ -0,0 +1,14 @@ +package gopher + +import cps._ + +trait IOChannel[F[_]:CpsAsyncMonad,I,O] extends IChannel[F,I] with OChannel[F,O]: + + protected def m: CpsAsyncMonad[F] = summon[CpsAsyncMonad[F]] + +end IOChannel + +trait Channel[F[_]:CpsAsyncMonad,A] extends IOChannel[F,A,A]: + + +end Channel diff --git a/shared/src/main/scala/gopher/ChannelClosedException.scala b/shared/src/main/scala/gopher/ChannelClosedException.scala new file mode 100644 index 00000000..b59b7aee --- /dev/null +++ b/shared/src/main/scala/gopher/ChannelClosedException.scala @@ -0,0 +1,3 @@ +package gopher + +class ChannelClosedException extends RuntimeException("channel is closed") diff --git a/shared/src/main/scala/gopher/Expirable.scala b/shared/src/main/scala/gopher/Expirable.scala new file mode 100644 index 00000000..bea508ad --- /dev/null +++ b/shared/src/main/scala/gopher/Expirable.scala @@ -0,0 +1,33 @@ +package gopher + +import cps._ + +trait Expirable[A]: + + /** + * called when reader/writer can become no more available for some reason + */ + def canExpire: Boolean + + /** + * if this object is expired and should be deleted from queue + * (for example: when reader is belong to select group and some other action in this select group was performed) + **/ + def isExpired: Boolean + + /** + * capture object, and after this we can or return one + **/ + def capture(): Option[A] + + /** + * Called when we submitt to task executor readFunction and now is safe to make exprire all other readers/writers in the + * same select group + **/ + def markUsed(): Unit + + /** + * Called when it was a race condition and we can't use captured function. + **/ + def markFree(): Unit + diff --git a/shared/src/main/scala/gopher/IChannel.scala b/shared/src/main/scala/gopher/IChannel.scala new file mode 100644 index 00000000..6d1cca3e --- /dev/null +++ b/shared/src/main/scala/gopher/IChannel.scala @@ -0,0 +1,46 @@ +package gopher + +import cps._ +import scala.util.Try +import scala.util.Success +import scala.util.Failure + +trait IChannel[F[_], A]: + + type Read = A + + def addReader(reader: Reader[A]): Unit + + // workarround for https://github.com/lampepfl/dotty/issues/10477 + protected def m: CpsAsyncMonad[F] + + def aread:F[A] = + m.adoptCallbackStyle(f => addReader(SimpleReader(f))) + + inline def read: A = await(aread)(using m) + + inline def ? : A = await(aread)(using m) + + def aOptRead: F[Option[A]] = + m.adoptCallbackStyle( f => + addReader(SimpleReader{ x => x match + case Failure(ex: ChannelClosedException) => f(Success(None)) + case Failure(ex) => f(Failure(ex)) + case Success(v) => f(Success(Some(v))) + }) + ) + + inline def optRead: Option[A] = await(aOptRead)(using m) + + class SimpleReader(f: Try[A] => Unit) extends Reader[A]: + + def canExpire: Boolean = false + def isExpired: Boolean = false + + def capture(): Option[Try[A]=>Unit] = Some(f) + + def markUsed(): Unit = () + def markFree(): Unit = () + + + diff --git a/shared/src/main/scala/gopher/OChannel.scala b/shared/src/main/scala/gopher/OChannel.scala new file mode 100644 index 00000000..04be16fe --- /dev/null +++ b/shared/src/main/scala/gopher/OChannel.scala @@ -0,0 +1,31 @@ +package gopher + +import cps._ + +import scala.util.Try + +trait OChannel[F[_]:CpsAsyncMonad, A]: + + + def awrite(a:A):F[Unit] = + summon[CpsAsyncMonad[F]].adoptCallbackStyle(f => + addWriter(SimpleWriter(a, f)) + ) + + inline def write(a:A): Unit = await(awrite(a)) + + def addWriter(writer: Writer[A]): Unit + + + class SimpleWriter(a:A, f: Try[Unit]=>Unit) extends Writer[A]: + + def canExpire: Boolean = false + + def isExpired: Boolean = false + + def capture(): Option[(A,Try[Unit]=>Unit)] = Some((a,f)) + + def markUsed(): Unit = () + + def markFree(): Unit = () + diff --git a/shared/src/main/scala/gopher/Reader.scala b/shared/src/main/scala/gopher/Reader.scala new file mode 100644 index 00000000..3a3fd312 --- /dev/null +++ b/shared/src/main/scala/gopher/Reader.scala @@ -0,0 +1,7 @@ +package gopher + +import scala.util.Try +import cps._ + +trait Reader[A] extends Expirable[Try[A]=>Unit] + diff --git a/shared/src/main/scala/gopher/SelectGroup.scala b/shared/src/main/scala/gopher/SelectGroup.scala new file mode 100644 index 00000000..7d5d7b60 --- /dev/null +++ b/shared/src/main/scala/gopher/SelectGroup.scala @@ -0,0 +1,70 @@ +package gopher + +import cps._ +import java.util.concurrent.atomic.AtomicInteger +import scala.util.Try + + + +trait SelectGroup[F[_]:CpsSchedulingMonad, S]: + + /** + * instance of select group created for call of select. + **/ + val waitState: AtomicInteger = new AtomicInteger(0) + var call: Try[S] => Unit = { _ => () } + private inline def m = summon[CpsSchedulingMonad[F]] + val retval = m.adoptCallbackStyle[S](f => call=f) + + + def addReader[A](ch: IChannel[F,A], action: Try[A]=>F[S]): Unit = + val record = ReaderRecord(ch, action) + ch.addReader(record) + + def addWriter[A](ch: OChannel[F,A], element: A, action: Try[Unit]=>F[S]): Unit = + val record = WriterRecord(ch, element, action) + ch.addWriter(record) + + def step():F[S] = + retval + + + trait Expiration: + def canExpire: Boolean = true + def isExpired: Boolean = waitState.get()==2 + def markUsed(): Unit = waitState.lazySet(2) + def markFree(): Unit = waitState.set(0) + + case class ReaderRecord[A](ch: IChannel[F,A], action: Try[A] => F[S]) extends Reader[A] with Expiration: + type Element = A + type State = S + + override def capture(): Option[Try[A]=>Unit] = + if waitState.compareAndSet(0,1) then + Some(v => { + m.spawn( + m.mapTry(action(v))(x => call(x)) + ) + }) + else + None + + + + case class WriterRecord[A](ch: OChannel[F,A], + element: A, + action: Try[Unit] => F[S], + ) extends Writer[A] with Expiration: + type Element = A + type State = S + + override def capture(): Option[(A,Try[Unit]=>Unit)] = + if waitState.compareAndSet(0,1) then + Some((element, (v:Try[Unit]) => m.spawn( + m.mapTry(action(v))(x=>call(x)) + ))) + else + None + + + diff --git a/shared/src/main/scala/gopher/Writer.scala b/shared/src/main/scala/gopher/Writer.scala new file mode 100644 index 00000000..a9db6a3a --- /dev/null +++ b/shared/src/main/scala/gopher/Writer.scala @@ -0,0 +1,6 @@ +package gopher + +import scala.util.Try + +trait Writer[A] extends Expirable[(A,Try[Unit]=>Unit)] + From 7b7562570183cde2e90fadeff9788670fb5b00a6 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Sat, 5 Dec 2020 08:10:52 +0200 Subject: [PATCH 002/161] first steps for 2.0.0 --- README.md | 430 +++--------------- build.sbt | 17 +- js/src/main/scala/gopher/Platform.scala | 6 + jvm/src/main/scala/gopher/JVMGopher.scala | 33 ++ .../main/scala/gopher/JVMGopherConfig.scala | 9 + jvm/src/main/scala/gopher/Platform.scala | 7 + .../gopher/impl/MTUnbufferedChannel.scala | 13 +- project/build.properties | 2 +- shared/src/main/scala/gopher/Gopher.scala | 16 + shared/src/main/scala/gopher/GopherAPI.scala | 46 ++ 10 files changed, 197 insertions(+), 382 deletions(-) create mode 100644 js/src/main/scala/gopher/Platform.scala create mode 100644 jvm/src/main/scala/gopher/JVMGopher.scala create mode 100644 jvm/src/main/scala/gopher/JVMGopherConfig.scala create mode 100644 jvm/src/main/scala/gopher/Platform.scala create mode 100644 shared/src/main/scala/gopher/Gopher.scala create mode 100644 shared/src/main/scala/gopher/GopherAPI.scala diff --git a/README.md b/README.md index b47eb43f..32143e9d 100644 --- a/README.md +++ b/README.md @@ -4,29 +4,21 @@ ### Dependences: - * scala 2.13.3 - * akka 2.6.8 - * scala-async +For scala 3.0.0-RC1: -#### Download: + libraryDependencies += "com.github.rssh" %% "scala-gopher" % "2.0.0-RC1" -For scala 2.13: +For scala2: libraryDependencies += "com.github.rssh" %% "scala-gopher" % "0.99.15" -(or `0.99.16-SNAPSHOT` for development version). - -For scala 2.12: - - libraryDependencies += "com.github.rssh" %% "scala-gopher" % "0.99.10" - Scala-gopher is open source (license is Apache2); binaries are available from the maven-central repository. ## Overview - Scala-gopher is a scala library, build on top of Akka and SIP-22 async, which provide an implementation of - CSP [Communicate Sequential Processes] primitives, known as 'Go-like channels.' Also, analogs of go/defer/recover control-flow constructions are provided. + Scala-gopher is a scala library, build on top of dotty-cps-async, which provide an implementation of + CSP [Communicate Sequential Processes] primitives, known as 'Go-like channels.' Note, which this is not an emulation of go language structures in Scala, but rather a reimplementation of the main ideas in 'scala-like' manner. @@ -34,125 +26,81 @@ Note, which this is not an emulation of go language structures in Scala, but rat ### Initialization - You need an instance of gopherApi for creating channels and selectors. The easiest way is to use one as Akka extension: + You need a given of gopherApi for creating channels and selectors. - import akka.actors._ import gopher._ ...... - val actorSystem = ActorSystem.create("system") - val gopherApi = Gopher(actorSystem) + given Gopher[Future]() - In akka.conf we can place config values in 'gopher' entry. - -## Control flow constructions: - -### goScope - - `goScope[T](body: =>T)` is expression, which allows to use inside `body` go-like 'defer' and 'recover' expression. + type parameter is a monad, which should implement CpsSchedulingMonad typeclass. - Typical usage: -~~~ scala -import gopher._ -import java.io._ - -object CopyFile { - - def main(args: Array[String]): Unit = { - if (args.length != 3) { - System.err.println("usage: copy in out"); - } - copy(new File(args(1)), new File(args(2))) - } - - def copy(inf: File, outf: File): Long = - goScope { - val in = new FileInputStream(inf) - defer { - in.close() - } - val out = new FileOutputStream(outf); - defer { - out.close() - } - out.getChannel() transferFrom(in.getChannel(), 0, Long.MaxValue) - } - -} -~~~ - Here statements inside defer block executed at the end of goScope block in reverse order. - - Inside goScope we can use two pseudo functions: - -* `defer(body: =>Unit):Unit` - defer execution of `body` until the end of `go` or `goScope` block and previous defered blocks. -* `recover[T](f:PartialFunction[Throwable,T]):Boolean` -- can be used only within `defer` block with next semantics: -* * if exception was raised inside `go` or `goScope` than `recover` try to apply `f` to this exception and -* * * if `f` is applicable - set `f(e)` as return value of the block and return true -* * * otherwise - do nothing and return false -* * during normal exit - return false. - -You can look on `defer` as on stackable finally clauses, and on `defer` with `recover` inside as on `catch` clause. Small example: - -~~~ scala -val s = goScope{ - defer{ recover{ - case ex: Throwable => "CCC" - } } - throw new Exception("") - "QQQ" - } -~~~ - - will set `s` to "CCC". - - - -### go - - `go[T](body: =>T)(implicit ex:ExecutionContext):Future[T]` starts asynchronous execution of `body` in provided execution context. Inside go we can use `defer`/`recover` clauses and blocked read/write channel operations. - - Go implemented on top of [SIP-22](http://docs.scala-lang.org/sips/pending/async.html) async and share the same limitations. In addition to async/await transfoirm `go` provide lifting up asynchronous expressions inside some well-known hight-order functions (i.e. it is possible to use async operations inside for loops). Details are available in the tech report: https://arxiv.org/abs/1611.00602 . - ## Channels Channels are used for asynchronous communication between execution flows. - -When using channel inside *go* block, you can look at one as on classic blocked queue with fixed size with methods read and write: +When using channel inside *async* block, you can look at one as on classic blocked queue with fixed size with methods read and write: - val channel = gopherApi.makeChannel[Int]; + val channel = makeChannel[Int]; - go { + async { channel.write(a) } ...... - go { + async { val i = channel.read } - * `channel.write(x)` - send x to channel and wait until one will be sent (it is possible us as synonyms `channel<~x` and `channel!x` if you prefer short syntax) * `channel.read` or `(channel ?)` - blocking read -Blocking operations can be used only inside `go` or `Async.await` blocks. +Blocking operations can be used only inside `await` blocks. Outside we can use asynchronous version: * `channel.awrite(x)` will write `x` and return to us `Future[Unit]` which will be executed after x will send * `channel.aread` will return future to the value, which will be read. -Also, channels can be closed. After this attempt to write will cause throwing 'ClosedChannelException.' Reading will be still possible up to 'last written value', after this attempt to read will cause the same exception. Also, each channel provides `done` input for firing close events. -Note, closing channels are not mandatory; unreachable channels are garbage-collected regardless of they are closed or not. +Channels can be closed. After this attempt to write will cause throwing 'ClosedChannelException.' Reading will be still possible up to 'last written value', after this attempt to read will cause the same exception. Also, each channel provides `done` input for firing close events. +Closing channels are not mandatory; unreachable channels are garbage-collected regardless of they are closed or not. Channels can be buffered and unbuffered. In a unbuffered channel, write return control to the caller after another side actually will start processing; buffered channel force provider to wait only if internal channel buffer is full. Also, you can use only `Input` or `Output` interfaces, where an appropriative read/write operations are defined. -For `Input`, exists usual collection functions, like `map`, `zip`, `takeN`, `fold` ... etc. Scala Iterable can be represented as `channels.Input` via method `gopherApi.iterableInput`. Also, we can use Scala futures as channels, which produce one value and then closes. For obtaining such input use `gopherApi.futureInput`. +For `Input`, exists usual collection functions, like `map`, `zip`, `takeN`, `fold` ... etc. + +For example, let we have the direct port of golang code: +~~~ scala + +val channel = gopher.makeChannel[Int](100) + +val producer = channel.awrite(1 to 1000) + +@volatile var sum = 0; +val consumer = async { + var done = false + while(!done) + val i = channel.read + sum = sum + i + if i==1000 then + done = true +} + +Await.ready(consumer, 5.second) +~~~ + +last loop can be repharased in more scala wat as: + +~~~ scala +val sum = (channel.take(1000)).fold(0)((s,i) => s+i) +~~~ + +Scala Iterable can be represented as `channels.Input` via method `gopherApi.iterableInput`. Also, we can use Scala futures as channels, which produce one value and then closes. For obtaining such input use `gopherApi.futureInput`. `|` (i.e. or) operator used for merged inputs, i.e. `(x|y).read` will read a value from channel x or y when one will be available. @@ -179,71 +127,38 @@ Also, note that you can provide own Input and Output implementations by implemen Gopher provides similar functionality: ~~~ scala -go{ - for( s <- gopherApi.select.forever) - s match { +async[Future]{ + while(!done) + select { case i:channelA.read => ..do-something-with-i case ch:channelB.read .. do-something-with-b - } + } } ~~~ Here we read in the loop from channelA or channelB. - Body of select loop must consist only of one `match` statement where - left parts in `case` clauses must have the following form + select accepts partial functions syntax, left parts in `case` clauses must have the following form * `v:channel.read` (for reading from channel) - * `v:Tye if (v==read(ch))` (for reading from channel or future) + * `v:Type if (v==read(ch))` (for reading from channel or future) * `v:channel.write if (v==expr)` (for writing `expr` into channel). * `v:Type if (v==write(ch,expr))` (for writing `expr` into channel). - * `_` - for 'idle' action. + * `_` - for 'idle' action in unblocking select. (TODO: .. ) - For endless loop inside `go` we can use the shortcut with the syntax of partial function: - -~~~ scala - gopherApi.select.forever{ - case i:channelA.read => ... do-something-with-i - case ch:channelB.read ... do-something-with-b - } -~~~ - - - Inside case actions, we can use blocking read/writes and await operations. Call of doExit in the implicit instance of `FlowTermination[T]` (for a forever loop this is `FlowTermination[Unit]`) can be used for exiting from the loop; `select.exit` and `select.shutdown` macroses are shortcuts for this. + Inside case actions, we can use blocking read/writes and await operations. Example: ~~~ scala -val channel = gopherApi.makeChannel[Int](100) - -val producer = channel.awrite(1 to 1000) - -@volatile var sum = 0; -val consumer = gopherApi.select.forever{ - case i: channerl.read => - sum = sum + i - if (i==1000) { - select.shutdown() - } -} - -Await.ready(consumer, 5.second) +TODO ~~~ A combination of variable and select loop better modeled with help 'fold over select' construction: ~~~ scala -val sum = gopherApi.select.afold(0) { (state, selector) => - selector match { - case i: channel.read => - val nstate = state + i - if (i==1000) { - select.exit(nstate) - } - nstate - } -} +TODO ~~~ @@ -268,66 +183,17 @@ val multiplexed = select amap { ~~~ - For using select operation not enclosed in a loop, scala-gopher provide - *select.once* syntax: - -~~~ scala -gopherApi.select.once{ - case i: channelA.read => s"Readed(${i})" - case x:channelB.write if (x==1) => s"Written(${x})" -} -~~~ - - - Such form can be called from any environment and will return `Future[String]`. Inside `go` you can wrap this in await of use 'for' syntax as with `forever`. - -~~~ scala -go { - ..... - val s = for(s <-gopherApi.select.once) - s match { - case i: channelA.read => s"Readed(${i})" - case x: channelB.write if (x==1) => s"Written(${x})" - } - -} -~~~ - - - and afold become fold: - -~~~ scala -go { - ... - val sum = select.fold(0) { (n,s) => - s match { - case x: channelA.read => n+x - case q: quit.read => select.exit(n) - } - } -} -~~~ - - amap - map - -~~~ scala -val multiplexed = for(s <- select) yield - s match { - case x:ch1.read => (s1,x) - case y:ch2.read => (s2,y) - } -~~~ - ## Done signals. Sometimes it is useful to receive a message when some `Input` becomes closed. Such inputs are named 'CloseableInputs' and provides a way to receive close notification in selector using `done` pseudo-type. ~~~ scala - select.foreach{ - case x:ch.read => Console.println(s"received: ${x}") - case _:ch.done => Console.println(s"done") - select.exit(()) - } + while(!done) + select{ + case x:ch.read => Console.println(s"received: ${x}") + case _:ch.done => Console.println(s"done") + done = true + } ~~~ Note, that you must exit from current flow in `done` handler, otherwise `done` signals will be intensively generated in a loop. @@ -364,179 +230,3 @@ Let's look at the example: Here in 'filter', we generate a set of prime numbers, and make a sieve of Eratosthenes by sequentially applying 'filter' effect to state of sieve EffectedInput. -## Transputers - - The logic of data transformation between channels can be encapsulated in special `Transputer` concept. (Word 'transputer' was chosen - as a reminder about INMOS processor, for which one of the first CSP languages, Occam, was developed). You can view on transputer as - a representation of a restartable process that consists from: - - * Set of named input and output ports. - * Logic for propagating information from the input to the output ports. - * Possible state - * Logic of error recovering. - -I.e. we saw that Transputer is similar to Actor with the following difference: - When Actor provides reaction to incoming messages from the mailbox and sending signals to other actors, Transputers provide processing of incoming messages from input ports and sending outcoming messages to output ports. When operations inside Actor must not be blocked, operations inside Transputer can wait. - -Transformers are build hierarchically with the help of 3 operations: - - * select (logic is execution of a select statement ) - * parallel combination (logic is parallel execution of parts) - * replication (logic is a parallel execution of a set of identical transformers.) - -### Select transputer - - Let's look at a simple example: transputer with two input ports and one output. -When the same number has come from `inA` and `inB`, then -transputer prints `Bingo` on console and output this number to `out`: - -~~~ scala - trait BingoTransputer extends SelectTransputer - { - val inA = InPort[Int] - val inB = InPort[Int] - val out = OutPort[Boolean] - - loop { - case x:inA.read => - y = inB.read - out.write(x==y) - if (x==y) { - Console.println(s"Bingo: ${x}") - } - } - - } -~~~ - - A select loop is described in `loop` statement. - - To create transputer we can use `gopherApi.makeTransputer` call: -~~~ scala -val bing = gopherApi.makeTransputer[BingoTransputer] -~~~ - after the creation of transputer, we can create channels, connect one to ports and start transformer. - -~~~ scala -val inA = makeChannel[Int]() -bingo.inA.connect(inA) -val inB = makeChannel[Int]() -bingo.inB.connect(inB) -val out = makeChannel[Int]() -bingo.out.connect(out) - -val shutdownFuture = bingo.start() -~~~ - - - Then after we will write to `inA` and `inB` values `(1,1)` then `true` will become available for reading from `out`. - -#### Error recovery - - On an exception from a loop statement, transputer will be restarted with ports, connected to the same channels. Such behavior is the default; we can configure one by setting recovery policy: - -~~~ scala -val t = makeTransputer[MyType].recover { - case ex: MyException => SupervisorStrategy.Escalate - } -~~~ - - Recovery policy is a partial function from throwable to akka `SupervisorStrategy.Direction`. Escalated exceptions are passed to parent transputers or to TransputerSupervisor actor, which handle failures according to akka default supervisor strategy. - - How many times transputer can be restarted within given period can be configured via failureLimit call: - -~~~ scala - t.failureLimit(maxFailures = 20, windowDuration = 10 seconds) -~~~ - - This setting means that if 20 failures will occur during 10 seconds, then exception Transputer.TooManyFailures will be escalated to parent. - -### Par transputers. - - 'Par' is a group of transputers running in parallel. Par transputer can be created with the help of plus operator: - -~~~ scala -val par = (t1 + t1 + t3) -par.start() -~~~ - - When one from `t1`, `t2`, ... is stopped or failed, then all other members of `par` are stopped. After this `par` can be restarted according to current recovery policy. - - -### Replication - - Replicated transputer is a set of identical transputers t_{i}, running in parallel. It can be created with `gopherApi.replicate` call. Next code fragment: - -~~~ scala -val r = gopherApi.replicate[MyTransputer](10) -~~~ - - will produce ten copies of MyTransputer (`r` will be a container transputer for them). Ports of all replicated internal transputers will be shared with ports of the container. (I.e. if we will write something to input port then it will be read by one of the replicas; if one of the replicas will write something to out port, this will be visible in out port of the container.) - - Mapping from a container to replica port can be changed from sharing to other approaches, like duplicating or distributing, via applying port transformations. - - For example, next code fragment: - -~~~ scala -r.inA.duplicate() - .inB.distribute( _.hashCode ) -~~~ - - will set port `inA` be duplicated in replicas (i.e. message, send to container port `inA` will be received by each instance) and messages from `inB` will be distributed by hashcode: i.e. messages with the same hashcode will be directed to the same replica. Such behavior is useful when we keep in replicated transputer some state information about messages. - - Stopping and recovering of replicated transformer is the same as in `par` (i.e. stopping/failing of one instance will cause stopping/failing of container) - - Also note, that we can receive a sequence of replicated instances with the help of `ReplicateTransformer.replicated` method. - -## Unsugared interfaces - - It is worth to know that exists gopher API without macro-based syntax sugar. - -~~~ scala -( - new ForeverSelectorBuilder(gopherApi) - .reading(ch1){ x => something-x } - .writing(ch2,y){ y => something-y } - .idle(something idle).go -) -~~~ - - can be used instead of appropriative macro-based call. - - Moreover, for tricky things exists even low-level interface, which can combine computations by adding to functional interfaces, similar to continuations: - -~~~ scala -{ - val selector = new Selector[Unit](gopherApi) - selector.addReader(ch1, cont=>Some{ in => something-x - Future successful cont - } - ) - selector.addWriter(ch2, cont=>Some{(y,{something y; - Future successful cont - })}) - selector.addIdle(cont => {..do-something-when-idle; Future successful cont}) -} -~~~ - - Please, consult with source code for details. - - -## Additional Informatiom - ---------------------- - -* API reference: http://rssh.github.io/scala-gopher/api/index.html#package -* source code: https://github.com/rssh/scala-gopher -* presentations: - * Odessa Java/Scala Labs; Kiev Scala Meetup: Oct. 2014: http://www.slideshare.net/rssh1/scala-gopher2014 - * Wix R&D meetup. Mart 2016: http://www.slideshare.net/rssh1/csp-scala-wixmeetup2016 - * Scala Symposium. Oct. 2016. Amsterdam. http://www.slideshare.net/rssh1/scalagopher-cspstyle-programming-techniques-with-idiomatic-scala -* techreport: https://arxiv.org/abs/1611.00602 - - - Some related links: - -* [Communicating Sequential Processes book by Tony Hoare](http://www.usingcsp.com) -* [brief history of CSP in Bell-labs](http://swtch.com/~rsc/thread/) -* [introduction article about go defer/recover](http://blog.golang.org/defer-panic-and-recover) - diff --git a/build.sbt b/build.sbt index 5c8f92d1..8e6c98cf 100644 --- a/build.sbt +++ b/build.sbt @@ -1,5 +1,6 @@ //val dottyVersion = "3.0.0-M1-bin-20201022-b26dbc4-NIGHTLY" -val dottyVersion = "3.0.0-RC1-bin-SNAPSHOT" +//val dottyVersion = "3.0.0-RC1-bin-SNAPSHOT" +val dottyVersion = "3.0.0-M2" //val dottyVersion = dottyLatestNightlyBuild.get @@ -8,9 +9,13 @@ val sharedSettings = Seq( organization := "com.github.rssh", scalaVersion := dottyVersion, name := "scala-gopher", - //resolvers += "Local Ivy Repository" at "file://"+Path.userHome.absolutePath+"/.ivy2/local", resolvers += "Local Ivy Repository" at "file://"+Path.userHome.absolutePath+"/.ivy2/local", - libraryDependencies += "com.github.rssh" %%% "dotty-cps-async" % "0.3.4-SNAPSHOT", + libraryDependencies += "com.github.rssh" %%% "dotty-cps-async" % "0.3.4p1-M2-SNAPSHOT", + // 0.7.5 for scaladoc is not exists + //libraryDependencies += "com.lihaoyi" %%% "utest" % "0.7.5" % "test", + //testFrameworks += new TestFramework("utest.runner.Framework") + libraryDependencies += "org.scalameta" %%% "munit" % "0.7.19" % Test, + testFrameworks += new TestFramework("munit.Framework") ) lazy val root = project @@ -29,10 +34,10 @@ lazy val gopher = crossProject(JSPlatform, JVMPlatform) .settings(sharedSettings) .disablePlugins(SitePlugin) .jvmSettings( - scalacOptions ++= Seq( "-unchecked" ), - libraryDependencies += "com.novocode" % "junit-interface" % "0.11" % "test", + scalacOptions ++= Seq( "-unchecked" ), ).jsSettings( + // TODO: switch to ModuleES ? + scalaJSLinkerConfig ~= { _.withModuleKind(ModuleKind.CommonJSModule) }, scalaJSUseMainModuleInitializer := true, - libraryDependencies += ("org.scala-js" %% "scalajs-junit-test-runtime" % "1.2.0" % Test).withDottyCompat(scalaVersion.value) ) diff --git a/js/src/main/scala/gopher/Platform.scala b/js/src/main/scala/gopher/Platform.scala new file mode 100644 index 00000000..83805450 --- /dev/null +++ b/js/src/main/scala/gopher/Platform.scala @@ -0,0 +1,6 @@ +package gopher + +object Platform: + + def initShared(): Unit = + SharedGopherAPI.setApi(???) \ No newline at end of file diff --git a/jvm/src/main/scala/gopher/JVMGopher.scala b/jvm/src/main/scala/gopher/JVMGopher.scala new file mode 100644 index 00000000..40dd97b9 --- /dev/null +++ b/jvm/src/main/scala/gopher/JVMGopher.scala @@ -0,0 +1,33 @@ +package gopher + +import cps._ +import gopher.impl._ + +import java.util.concurrent.Executors +import java.util.concurrent.ExecutorService +import java.util.concurrent.ForkJoinPool + + + +class JVMGopher[F[_]:CpsSchedulingMonad](cfg: JVMGopherConfig) extends Gopher[F]: + + + def makeChannel[A](bufSize:Int) = + if (bufSize == 1) + MTUnbufferedChannel[F,A](cfg.controlExecutor,cfg.taskExecutor) + else + ??? + +object JVMGopher extends GopherAPI: + + def apply[F[_]:CpsSchedulingMonad](cfg: GopherConfig):Gopher[F] = + val jvmConfig = cfg match + case DefaultGopherConfig => defaultConfig + case jcfg:JVMGopherConfig => jcfg + new JVMGopher[F](jvmConfig) + + lazy val defaultConfig=JVMGopherConfig( + controlExecutor=Executors.newFixedThreadPool(2), + taskExecutor=ForkJoinPool.commonPool() + ) + diff --git a/jvm/src/main/scala/gopher/JVMGopherConfig.scala b/jvm/src/main/scala/gopher/JVMGopherConfig.scala new file mode 100644 index 00000000..cbe4ab26 --- /dev/null +++ b/jvm/src/main/scala/gopher/JVMGopherConfig.scala @@ -0,0 +1,9 @@ +package gopher + +import java.util.concurrent.ExecutorService + + +case class JVMGopherConfig( + controlExecutor: ExecutorService, + taskExecutor: ExecutorService +) extends GopherConfig diff --git a/jvm/src/main/scala/gopher/Platform.scala b/jvm/src/main/scala/gopher/Platform.scala new file mode 100644 index 00000000..82db07cd --- /dev/null +++ b/jvm/src/main/scala/gopher/Platform.scala @@ -0,0 +1,7 @@ +package gopher + + +object Platform: + + def initShared(): Unit = + SharedGopherAPI.setApi(JVMGopher) \ No newline at end of file diff --git a/jvm/src/main/scala/gopher/impl/MTUnbufferedChannel.scala b/jvm/src/main/scala/gopher/impl/MTUnbufferedChannel.scala index 818892e9..1dfa0480 100644 --- a/jvm/src/main/scala/gopher/impl/MTUnbufferedChannel.scala +++ b/jvm/src/main/scala/gopher/impl/MTUnbufferedChannel.scala @@ -13,13 +13,14 @@ import scala.util.Try import scala.util.Success import scala.util.Failure -class MTUnbufferedChannel[F[_]:CpsAsyncMonad,A <: AnyRef](controlExecutor: ExecutorService, taskExecutor: ExecutorService) extends Channel[F,A] with IOChannel[F,A,A] with OChannel[F,A] with IChannel[F,A]: +class MTUnbufferedChannel[F[_]:CpsAsyncMonad,A](controlExecutor: ExecutorService, taskExecutor: ExecutorService) extends Channel[F,A] with IOChannel[F,A,A] with OChannel[F,A] with IChannel[F,A]: private val readers = new ConcurrentLinkedDeque[Reader[A]]() private val writers = new ConcurrentLinkedDeque[Writer[A]]() - private val ref = new AtomicReference[A|Null](null) + private val ref = new AtomicReference[AnyRef|Null](null) private val isClosed = new AtomicBoolean(false) private val stepRunnable: Runnable = (()=>step()) + def addReader(reader: Reader[A]): Unit = if (reader.canExpire) then @@ -40,7 +41,7 @@ class MTUnbufferedChannel[F[_]:CpsAsyncMonad,A <: AnyRef](controlExecutor: Execu var progress = true while(progress) progress = false - val a: A|Null = ref.get() + val a: AnyRef|Null = ref.get() if !(a eq null) then val reader = readers.poll() if !(reader eq null) then @@ -48,7 +49,8 @@ class MTUnbufferedChannel[F[_]:CpsAsyncMonad,A <: AnyRef](controlExecutor: Execu reader.capture() match case Some(f) => if (ref.compareAndSet(a,null)) - taskExecutor.execute( ()=>f(Success(a.nn)) ) // TODO: what if f throws exception [?] + // TODO: explicit boxing/unboxing ? + taskExecutor.execute( ()=>f(Success(a.nn.asInstanceOf[A])) ) // TODO: what if f throws exception [?] reader.markUsed() else // somebody from other thread stole our 'a', so need to try again @@ -71,7 +73,8 @@ class MTUnbufferedChannel[F[_]:CpsAsyncMonad,A <: AnyRef](controlExecutor: Execu progress = true writer.capture() match case Some((a,f)) => - if (ref.compareAndSet(null,a)) + val ar = a.asInstanceOf[AnyRef] + if (ref.compareAndSet(null,ar)) taskExecutor.execute( () => f(Success(())) ) writer.markUsed() else diff --git a/project/build.properties b/project/build.properties index c19c768d..7de0a938 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=1.4.2 +sbt.version=1.4.4 diff --git a/shared/src/main/scala/gopher/Gopher.scala b/shared/src/main/scala/gopher/Gopher.scala new file mode 100644 index 00000000..8f378a9c --- /dev/null +++ b/shared/src/main/scala/gopher/Gopher.scala @@ -0,0 +1,16 @@ +package gopher + +import cps._ + +trait Gopher[F[_]:CpsSchedulingMonad]: + + type Monad[X] = F[X] + + def makeChannel[A](bufSize:Int = 1): Channel[F,A] + + + + +def makeChannel[A](bufSize:Int = 1)(using g:Gopher[?]):Channel[g.Monad,A] = + g.makeChannel(bufSize) + diff --git a/shared/src/main/scala/gopher/GopherAPI.scala b/shared/src/main/scala/gopher/GopherAPI.scala new file mode 100644 index 00000000..6fe67caf --- /dev/null +++ b/shared/src/main/scala/gopher/GopherAPI.scala @@ -0,0 +1,46 @@ +package gopher + +import cps._ + +trait GopherConfig +case object DefaultGopherConfig extends GopherConfig + + + +trait GopherAPI: + + + def apply[F[_]:CpsSchedulingMonad](cfg:GopherConfig = DefaultGopherConfig): Gopher[F] + + + + +/** + * Shared gopehr api, which is initialized by platofrm part, + * Primary used for cross-platforming test, you shoul initialize one of platform API + * behind and then run tests. + **/ +object SharedGopherAPI { + + private[this] var _api: Option[GopherAPI] = None + + def apply[F[_]:CpsSchedulingMonad](cfg:GopherConfig = DefaultGopherConfig): Gopher[F] = + api.apply[F](cfg) + + + def api: GopherAPI = + if (_api.isEmpty) then + initPlatformSpecific() + _api.get + + + + private[gopher] def setApi(api: GopherAPI): Unit = + this._api = Some(api) + println("SharedGopherAPi") + + + private[gopher] def initPlatformSpecific(): Unit = + Platform.initShared() + +} \ No newline at end of file From 9f73d657a7cd0409061ce7bda821ef861efd6cd8 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Sat, 5 Dec 2020 11:55:43 +0200 Subject: [PATCH 003/161] 1-st minimal shared test --- js/src/main/scala/gopher/JSGopher.scala | 21 +++++ js/src/main/scala/gopher/JSGopherConfig.scala | 3 + js/src/main/scala/gopher/Platform.scala | 2 +- .../scala/gopher/impl/UnbufferedChannel.scala | 78 +++++++++++++++++++ .../gopher/impl/MTUnbufferedChannel.scala | 3 +- .../test/scala/gopher/ApiAccessTests.scala | 26 +++++++ shared/src/main/scala/gopher/Channel.scala | 9 +-- shared/src/main/scala/gopher/Gopher.scala | 6 +- .../{IChannel.scala => ReadChannel.scala} | 19 +++-- .../src/main/scala/gopher/SelectGroup.scala | 8 +- .../{OChannel.scala => WriteChannel.scala} | 9 ++- .../test/scala/hofasync/TestSharedMin.scala | 27 +++++++ 12 files changed, 184 insertions(+), 27 deletions(-) create mode 100644 js/src/main/scala/gopher/JSGopher.scala create mode 100644 js/src/main/scala/gopher/JSGopherConfig.scala create mode 100644 js/src/main/scala/gopher/impl/UnbufferedChannel.scala create mode 100644 jvm/src/test/scala/gopher/ApiAccessTests.scala rename shared/src/main/scala/gopher/{IChannel.scala => ReadChannel.scala} (69%) rename shared/src/main/scala/gopher/{OChannel.scala => WriteChannel.scala} (70%) create mode 100644 shared/src/test/scala/hofasync/TestSharedMin.scala diff --git a/js/src/main/scala/gopher/JSGopher.scala b/js/src/main/scala/gopher/JSGopher.scala new file mode 100644 index 00000000..a9a7f074 --- /dev/null +++ b/js/src/main/scala/gopher/JSGopher.scala @@ -0,0 +1,21 @@ +package gopher + +import cps._ + +class JSGopher[F[_]:CpsSchedulingMonad](cfg: JSGopherConfig) extends Gopher[F]: + + + def makeChannel[A](bufSize:Int) = + if (bufSize == 1) + new impl.UnbufferedChannel[F,A](this) + else + ??? + +object JSGopher extends GopherAPI: + + def apply[F[_]:CpsSchedulingMonad](cfg: GopherConfig):Gopher[F] = + val jsConfig = cfg match + case DefaultGopherConfig => JSGopherConfig("default") + case jcfg:JSGopherConfig => jcfg + new JSGopher[F](jsConfig) + diff --git a/js/src/main/scala/gopher/JSGopherConfig.scala b/js/src/main/scala/gopher/JSGopherConfig.scala new file mode 100644 index 00000000..741810c3 --- /dev/null +++ b/js/src/main/scala/gopher/JSGopherConfig.scala @@ -0,0 +1,3 @@ +package gopher + +case class JSGopherConfig(flawor: String = "default") extends GopherConfig \ No newline at end of file diff --git a/js/src/main/scala/gopher/Platform.scala b/js/src/main/scala/gopher/Platform.scala index 83805450..b2d2be95 100644 --- a/js/src/main/scala/gopher/Platform.scala +++ b/js/src/main/scala/gopher/Platform.scala @@ -3,4 +3,4 @@ package gopher object Platform: def initShared(): Unit = - SharedGopherAPI.setApi(???) \ No newline at end of file + SharedGopherAPI.setApi(JSGopher) \ No newline at end of file diff --git a/js/src/main/scala/gopher/impl/UnbufferedChannel.scala b/js/src/main/scala/gopher/impl/UnbufferedChannel.scala new file mode 100644 index 00000000..6012c7d7 --- /dev/null +++ b/js/src/main/scala/gopher/impl/UnbufferedChannel.scala @@ -0,0 +1,78 @@ +package gopher.impl + +import cps._ +import gopher._ +import scala.collection.mutable.Queue +import scala.scalajs.concurrent.JSExecutionContext +import scala.util._ +import scala.util.control.NonFatal + + +class UnbufferedChannel[F[_]:CpsAsyncMonad, A](gopherApi: JSGopher[F]) extends Channel[F,A,A]: + + private val readers: Queue[Reader[A]] = Queue.empty + private val writers: Queue[Writer[A]] = Queue.empty + + private var value: Option[A] = None + private var closed: Boolean = false + + protected override def asyncMonad: cps.CpsAsyncMonad[F] = summon[CpsAsyncMonad[F]] + + def addReader(reader: Reader[A]): Unit = + readers.enqueue(reader) + process() + + def addWriter(writer: Writer[A]): Unit = + writers.enqueue(writer) + process() + + private def process(): Unit = + var progress = true + while(progress) + value match + case Some(a) => progress = processReaders(a) + case None => progress = processWriters() + + private def processReaders(a:A): Boolean = + // precondition: value == Some() + var progress = false + if (!readers.isEmpty) then + val reader = readers.dequeue() + progress = true + reader.capture() match + case Some(f) => + value = None + JSExecutionContext.queue.execute{()=> + try + f(Success(a)) + catch + case ex: Throwable => + // TODO: set execution handler. + ex.printStackTrace() + if (false) { + JSExecutionContext.queue.execute{ ()=>throw ex } + } + } + case None => + progress + + private def processWriters(): Boolean = + var progress = false + if (!writers.isEmpty) then + val writer = writers.dequeue() + writer.capture() match + case Some((a,f)) => + value = Some(a) + JSExecutionContext.queue.execute{ () => + try + f(Success(())) + catch + case NonFatal(ex) => + ex.printStackTrace() + if (false) { + JSExecutionContext.queue.execute( ()=> throw ex ) + } + } + case None => + // skip + progress \ No newline at end of file diff --git a/jvm/src/main/scala/gopher/impl/MTUnbufferedChannel.scala b/jvm/src/main/scala/gopher/impl/MTUnbufferedChannel.scala index 1dfa0480..0ebd29b9 100644 --- a/jvm/src/main/scala/gopher/impl/MTUnbufferedChannel.scala +++ b/jvm/src/main/scala/gopher/impl/MTUnbufferedChannel.scala @@ -13,7 +13,7 @@ import scala.util.Try import scala.util.Success import scala.util.Failure -class MTUnbufferedChannel[F[_]:CpsAsyncMonad,A](controlExecutor: ExecutorService, taskExecutor: ExecutorService) extends Channel[F,A] with IOChannel[F,A,A] with OChannel[F,A] with IChannel[F,A]: +class MTUnbufferedChannel[F[_]:CpsAsyncMonad,A](controlExecutor: ExecutorService, taskExecutor: ExecutorService) extends Channel[F,A,A]: private val readers = new ConcurrentLinkedDeque[Reader[A]]() private val writers = new ConcurrentLinkedDeque[Writer[A]]() @@ -21,6 +21,7 @@ class MTUnbufferedChannel[F[_]:CpsAsyncMonad,A](controlExecutor: ExecutorService private val isClosed = new AtomicBoolean(false) private val stepRunnable: Runnable = (()=>step()) + override protected def asyncMonad: CpsAsyncMonad[F] = summon[CpsAsyncMonad[F]] def addReader(reader: Reader[A]): Unit = if (reader.canExpire) then diff --git a/jvm/src/test/scala/gopher/ApiAccessTests.scala b/jvm/src/test/scala/gopher/ApiAccessTests.scala new file mode 100644 index 00000000..350ef800 --- /dev/null +++ b/jvm/src/test/scala/gopher/ApiAccessTests.scala @@ -0,0 +1,26 @@ +package gopher + + +import scala.concurrent._ +import scala.concurrent.duration._ +import cps._ +import cps.monads.FutureAsyncMonad +import scala.language.postfixOps + + + +class ApiAccessTests extends munit.FunSuite { + + + import scala.concurrent.ExecutionContext.Implicits.global + + test("simple channel") { + given Gopher[Future] = JVMGopher[Future]() + val ch = makeChannel[Int](1) + val fw1 = ch.awrite(1) + val fr1 = ch.aread + val r1 = Await.result(fr1, 1 second) + assert( r1 == 1 ) + } + +} \ No newline at end of file diff --git a/shared/src/main/scala/gopher/Channel.scala b/shared/src/main/scala/gopher/Channel.scala index b4c4fae9..4244106a 100644 --- a/shared/src/main/scala/gopher/Channel.scala +++ b/shared/src/main/scala/gopher/Channel.scala @@ -2,13 +2,12 @@ package gopher import cps._ -trait IOChannel[F[_]:CpsAsyncMonad,I,O] extends IChannel[F,I] with OChannel[F,O]: +trait Channel[F[_],W,R] extends ReadChannel[F,R] with WriteChannel[F,W]: - protected def m: CpsAsyncMonad[F] = summon[CpsAsyncMonad[F]] + override protected def asyncMonad: CpsAsyncMonad[F] -end IOChannel - -trait Channel[F[_]:CpsAsyncMonad,A] extends IOChannel[F,A,A]: + //protected override def asyncMonad: CpsAsyncMonad[F] end Channel + diff --git a/shared/src/main/scala/gopher/Gopher.scala b/shared/src/main/scala/gopher/Gopher.scala index 8f378a9c..37a52884 100644 --- a/shared/src/main/scala/gopher/Gopher.scala +++ b/shared/src/main/scala/gopher/Gopher.scala @@ -6,11 +6,9 @@ trait Gopher[F[_]:CpsSchedulingMonad]: type Monad[X] = F[X] - def makeChannel[A](bufSize:Int = 1): Channel[F,A] + def makeChannel[A](bufSize:Int = 1): Channel[F,A,A] - - -def makeChannel[A](bufSize:Int = 1)(using g:Gopher[?]):Channel[g.Monad,A] = +def makeChannel[A](bufSize:Int = 1)(using g:Gopher[?]):Channel[g.Monad,A,A] = g.makeChannel(bufSize) diff --git a/shared/src/main/scala/gopher/IChannel.scala b/shared/src/main/scala/gopher/ReadChannel.scala similarity index 69% rename from shared/src/main/scala/gopher/IChannel.scala rename to shared/src/main/scala/gopher/ReadChannel.scala index 6d1cca3e..739412f5 100644 --- a/shared/src/main/scala/gopher/IChannel.scala +++ b/shared/src/main/scala/gopher/ReadChannel.scala @@ -5,24 +5,27 @@ import scala.util.Try import scala.util.Success import scala.util.Failure -trait IChannel[F[_], A]: +trait ReadChannel[F[_], A]: type Read = A + // workarround for https://github.com/lampepfl/dotty/issues/10477 + protected def asyncMonad: CpsAsyncMonad[F] + + protected def rAsyncMonad: CpsAsyncMonad[F] = asyncMonad + def addReader(reader: Reader[A]): Unit - // workarround for https://github.com/lampepfl/dotty/issues/10477 - protected def m: CpsAsyncMonad[F] def aread:F[A] = - m.adoptCallbackStyle(f => addReader(SimpleReader(f))) + asyncMonad.adoptCallbackStyle(f => addReader(SimpleReader(f))) - inline def read: A = await(aread)(using m) + inline def read: A = await(aread)(using rAsyncMonad) - inline def ? : A = await(aread)(using m) + inline def ? : A = await(aread)(using rAsyncMonad) def aOptRead: F[Option[A]] = - m.adoptCallbackStyle( f => + asyncMonad.adoptCallbackStyle( f => addReader(SimpleReader{ x => x match case Failure(ex: ChannelClosedException) => f(Success(None)) case Failure(ex) => f(Failure(ex)) @@ -30,7 +33,7 @@ trait IChannel[F[_], A]: }) ) - inline def optRead: Option[A] = await(aOptRead)(using m) + inline def optRead: Option[A] = await(aOptRead)(using rAsyncMonad) class SimpleReader(f: Try[A] => Unit) extends Reader[A]: diff --git a/shared/src/main/scala/gopher/SelectGroup.scala b/shared/src/main/scala/gopher/SelectGroup.scala index 7d5d7b60..501c38c7 100644 --- a/shared/src/main/scala/gopher/SelectGroup.scala +++ b/shared/src/main/scala/gopher/SelectGroup.scala @@ -17,11 +17,11 @@ trait SelectGroup[F[_]:CpsSchedulingMonad, S]: val retval = m.adoptCallbackStyle[S](f => call=f) - def addReader[A](ch: IChannel[F,A], action: Try[A]=>F[S]): Unit = + def addReader[A](ch: ReadChannel[F,A], action: Try[A]=>F[S]): Unit = val record = ReaderRecord(ch, action) ch.addReader(record) - def addWriter[A](ch: OChannel[F,A], element: A, action: Try[Unit]=>F[S]): Unit = + def addWriter[A](ch: WriteChannel[F,A], element: A, action: Try[Unit]=>F[S]): Unit = val record = WriterRecord(ch, element, action) ch.addWriter(record) @@ -35,7 +35,7 @@ trait SelectGroup[F[_]:CpsSchedulingMonad, S]: def markUsed(): Unit = waitState.lazySet(2) def markFree(): Unit = waitState.set(0) - case class ReaderRecord[A](ch: IChannel[F,A], action: Try[A] => F[S]) extends Reader[A] with Expiration: + case class ReaderRecord[A](ch: ReadChannel[F,A], action: Try[A] => F[S]) extends Reader[A] with Expiration: type Element = A type State = S @@ -51,7 +51,7 @@ trait SelectGroup[F[_]:CpsSchedulingMonad, S]: - case class WriterRecord[A](ch: OChannel[F,A], + case class WriterRecord[A](ch: WriteChannel[F,A], element: A, action: Try[Unit] => F[S], ) extends Writer[A] with Expiration: diff --git a/shared/src/main/scala/gopher/OChannel.scala b/shared/src/main/scala/gopher/WriteChannel.scala similarity index 70% rename from shared/src/main/scala/gopher/OChannel.scala rename to shared/src/main/scala/gopher/WriteChannel.scala index 04be16fe..e0e6b8e2 100644 --- a/shared/src/main/scala/gopher/OChannel.scala +++ b/shared/src/main/scala/gopher/WriteChannel.scala @@ -4,15 +4,16 @@ import cps._ import scala.util.Try -trait OChannel[F[_]:CpsAsyncMonad, A]: +trait WriteChannel[F[_], A]: + protected def asyncMonad: CpsAsyncMonad[F] def awrite(a:A):F[Unit] = - summon[CpsAsyncMonad[F]].adoptCallbackStyle(f => + asyncMonad.adoptCallbackStyle(f => addWriter(SimpleWriter(a, f)) - ) + ) - inline def write(a:A): Unit = await(awrite(a)) + inline def write(a:A): Unit = await(awrite(a))(using asyncMonad) def addWriter(writer: Writer[A]): Unit diff --git a/shared/src/test/scala/hofasync/TestSharedMin.scala b/shared/src/test/scala/hofasync/TestSharedMin.scala new file mode 100644 index 00000000..b8440c29 --- /dev/null +++ b/shared/src/test/scala/hofasync/TestSharedMin.scala @@ -0,0 +1,27 @@ +package hofasync + +import cps._ +import cps.monads.FutureAsyncMonad +import gopher._ +import scala.concurrent.Future + + + +class TestSharedMin extends munit.FunSuite { + + test("snhared-init") { + import scala.concurrent.ExecutionContext.Implicits.global + val gopherApi = SharedGopherAPI.apply[Future]() + val ch = gopherApi.makeChannel[Int](1) + val fw1 = ch.awrite(2) + val fr1 = ch.aread + //implicit val printCode = cps.macroFlags.PrintCode + async[Future] { + val r1 = await(fr1) + assert( r1 == 2 ) + } + } + +} + + From 277a8ac73533bb9a89a41fd76d4c7b895689ad8b Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Sat, 5 Dec 2020 20:10:47 +0200 Subject: [PATCH 004/161] implemented BufferedChannel for js --- js/src/main/scala/gopher/JSGopher.scala | 6 +- .../main/scala/gopher/impl/BaseChannel.scala | 88 +++++++++++++++++++ .../scala/gopher/impl/BufferedChannel.scala | 65 ++++++++++++++ .../scala/gopher/impl/UnbufferedChannel.scala | 72 +++------------ .../scala/gopher/impl/RingBufferTest.scala | 38 ++++++++ shared/src/main/scala/gopher/Channel.scala | 2 +- 6 files changed, 205 insertions(+), 66 deletions(-) create mode 100644 js/src/main/scala/gopher/impl/BaseChannel.scala create mode 100644 js/src/main/scala/gopher/impl/BufferedChannel.scala create mode 100644 js/src/test/scala/gopher/impl/RingBufferTest.scala diff --git a/js/src/main/scala/gopher/JSGopher.scala b/js/src/main/scala/gopher/JSGopher.scala index a9a7f074..1bb42f10 100644 --- a/js/src/main/scala/gopher/JSGopher.scala +++ b/js/src/main/scala/gopher/JSGopher.scala @@ -6,10 +6,10 @@ class JSGopher[F[_]:CpsSchedulingMonad](cfg: JSGopherConfig) extends Gopher[F]: def makeChannel[A](bufSize:Int) = - if (bufSize == 1) - new impl.UnbufferedChannel[F,A](this) + if (bufSize == 1) then + impl.UnbufferedChannel[F,A](this) else - ??? + impl.BufferedChannel[F,A](this,bufSize) object JSGopher extends GopherAPI: diff --git a/js/src/main/scala/gopher/impl/BaseChannel.scala b/js/src/main/scala/gopher/impl/BaseChannel.scala new file mode 100644 index 00000000..c98def5a --- /dev/null +++ b/js/src/main/scala/gopher/impl/BaseChannel.scala @@ -0,0 +1,88 @@ +package gopher.impl + +import cps._ +import gopher._ +import scala.collection.mutable.Queue +import scala.scalajs.concurrent.JSExecutionContext +import scala.util._ +import scala.util.control.NonFatal + + +abstract class BaseChannel[F[_]:CpsAsyncMonad,A](root: JSGopher[F]) extends Channel[F,A,A]: + + protected val readers: Queue[Reader[A]] = Queue.empty + protected val writers: Queue[Writer[A]] = Queue.empty + protected var closed: Boolean = false + + protected override def asyncMonad: cps.CpsAsyncMonad[F] = summon[CpsAsyncMonad[F]] + + protected def internalDequeuePeek(): Option[A] + protected def internalDequeueFinish(): Unit + protected def internalEnqueue(a:A): Boolean + + + protected def submitTask(f: ()=>Unit ): Unit = + JSExecutionContext.queue.execute{ () => + try + f() + catch + case NonFatal(ex) => + if (true) then + ex.printStackTrace() + if (false) then + JSExecutionContext.queue.execute( ()=> throw ex ) + } + + def addReader(reader: Reader[A]): Unit = + if (closed) { + reader.capture().foreach{ f => + submitTask( () => + f(Failure(new ChannelClosedException())) + ) + } + } else { + readers.enqueue(reader) + process() + } + + def addWriter(writer: Writer[A]): Unit = + if (closed) { + writer.capture().foreach{ (a,f) => + submitTask( () => + f(Failure(new ChannelClosedException())) + ) + } + } else { + writers.enqueue(writer) + process() + } + + + + private def process(): Unit = + var progress = true + while(progress) + internalDequeuePeek() match + case Some(a) => progress = processReaders(a) + case None => progress = processWriters() + + protected def processReaders(a:A): Boolean = + var progress = false + if (!readers.isEmpty) then + val reader = readers.dequeue() + progress = true + reader.capture().foreach{ f => + internalDequeueFinish() + submitTask( () => f(Success(a)) ) + } + progress + + protected def processWriters(): Boolean = + var progress = false + if (!writers.isEmpty) then + val writer = writers.dequeue() + writer.capture().foreach{ case (a,f) => + internalEnqueue(a) + f(Success(())) + } + progress \ No newline at end of file diff --git a/js/src/main/scala/gopher/impl/BufferedChannel.scala b/js/src/main/scala/gopher/impl/BufferedChannel.scala new file mode 100644 index 00000000..02567674 --- /dev/null +++ b/js/src/main/scala/gopher/impl/BufferedChannel.scala @@ -0,0 +1,65 @@ +package gopher.impl + +import cps._ +import gopher._ +import scala.collection.mutable.Queue +import scalajs.js +import scalajs.concurrent.JSExecutionContext +import scala.util._ +import scala.util.control.NonFatal + +class BufferedChannel[F[_]:CpsAsyncMonad, A](gopherApi: JSGopher[F], bufSize: Int) extends BaseChannel[F,A](gopherApi): + + val ringBuffer: js.Array[A] = new js.Array[A](bufSize+1) + var start: Int = 0 + var end: Int = 0 + + // [1] [2] [3] + // ˆ ˆ + + def isEmpty = (start == end) + + def nElements = if (end > start) then { + end - start + } else if (start < end) then { + bufSize - start + end + } else 0 + + def isFull = (nElements == bufSize) + + protected override def internalDequeuePeek(): Option[A] = + if isEmpty then None else Some(ringBuffer(start)) + + protected override def internalDequeueFinish(): Unit = + start = (start + 1) % bufSize + + protected def internalEnqueue(a:A): Boolean = + if (start < end) then + if (end <= bufSize) then + ringBuffer(end) = a + end = end + 1 + true + else if (start == 0) then + false + else + ringBuffer(end) = a + end = 0 + true + else + if (end < start - 1) then + ringBuffer(end) = a + end = end + 1 + true + else + false + + + + + + + + + + + diff --git a/js/src/main/scala/gopher/impl/UnbufferedChannel.scala b/js/src/main/scala/gopher/impl/UnbufferedChannel.scala index 6012c7d7..9605ac36 100644 --- a/js/src/main/scala/gopher/impl/UnbufferedChannel.scala +++ b/js/src/main/scala/gopher/impl/UnbufferedChannel.scala @@ -8,71 +8,19 @@ import scala.util._ import scala.util.control.NonFatal -class UnbufferedChannel[F[_]:CpsAsyncMonad, A](gopherApi: JSGopher[F]) extends Channel[F,A,A]: - - private val readers: Queue[Reader[A]] = Queue.empty - private val writers: Queue[Writer[A]] = Queue.empty +class UnbufferedChannel[F[_]:CpsAsyncMonad, A](gopherApi: JSGopher[F]) extends BaseChannel[F,A](gopherApi): private var value: Option[A] = None - private var closed: Boolean = false - - protected override def asyncMonad: cps.CpsAsyncMonad[F] = summon[CpsAsyncMonad[F]] - def addReader(reader: Reader[A]): Unit = - readers.enqueue(reader) - process() - - def addWriter(writer: Writer[A]): Unit = - writers.enqueue(writer) - process() + protected override def internalDequeuePeek(): Option[A] = value + + protected override def internalDequeueFinish(): Unit = + value = None - private def process(): Unit = - var progress = true - while(progress) - value match - case Some(a) => progress = processReaders(a) - case None => progress = processWriters() + protected def internalEnqueue(a:A): Boolean = + value match + case None => value = Some(a) + true + case Some(a1) => false - private def processReaders(a:A): Boolean = - // precondition: value == Some() - var progress = false - if (!readers.isEmpty) then - val reader = readers.dequeue() - progress = true - reader.capture() match - case Some(f) => - value = None - JSExecutionContext.queue.execute{()=> - try - f(Success(a)) - catch - case ex: Throwable => - // TODO: set execution handler. - ex.printStackTrace() - if (false) { - JSExecutionContext.queue.execute{ ()=>throw ex } - } - } - case None => - progress - private def processWriters(): Boolean = - var progress = false - if (!writers.isEmpty) then - val writer = writers.dequeue() - writer.capture() match - case Some((a,f)) => - value = Some(a) - JSExecutionContext.queue.execute{ () => - try - f(Success(())) - catch - case NonFatal(ex) => - ex.printStackTrace() - if (false) { - JSExecutionContext.queue.execute( ()=> throw ex ) - } - } - case None => - // skip - progress \ No newline at end of file diff --git a/js/src/test/scala/gopher/impl/RingBufferTest.scala b/js/src/test/scala/gopher/impl/RingBufferTest.scala new file mode 100644 index 00000000..ab04eb85 --- /dev/null +++ b/js/src/test/scala/gopher/impl/RingBufferTest.scala @@ -0,0 +1,38 @@ +package gopher.impl + +import cps._ +import cps.monads.FutureAsyncMonad +import gopher._ +import scala.concurrent._ +import scalajs.concurrent.JSExecutionContext +import scalajs.concurrent.JSExecutionContext.Implicits.queue + + +import munit._ + +class RingBufferTests extends munit.FunSuite{ + + test("ring buffer ") { + + val gopher = JSGopher[Future](JSGopherConfig()) + val ch = gopher.makeChannel[Int](3) + + var x = 0 + + async[Future] { + ch.write(1) + ch.write(2) + ch.write(3) + // we should be blocked before sending next + JSExecutionContext.queue.execute{ () => + x = 1 + ch.aread + } + ch.write(4) + assert(x != 0) + } + + } + + +} \ No newline at end of file diff --git a/shared/src/main/scala/gopher/Channel.scala b/shared/src/main/scala/gopher/Channel.scala index 4244106a..402ecb46 100644 --- a/shared/src/main/scala/gopher/Channel.scala +++ b/shared/src/main/scala/gopher/Channel.scala @@ -2,7 +2,7 @@ package gopher import cps._ -trait Channel[F[_],W,R] extends ReadChannel[F,R] with WriteChannel[F,W]: +trait Channel[F[_],W,R] extends WriteChannel[F,W] with ReadChannel[F,R]: override protected def asyncMonad: CpsAsyncMonad[F] From 3c91d5f6417dd7db3686415b5ce7b45938899ca1 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Sun, 6 Dec 2020 23:38:58 +0200 Subject: [PATCH 005/161] initial jvm channel implementations --- build.sbt | 3 - .../main/scala/gopher/impl/BaseChannel.scala | 12 +- jvm/src/main/scala/gopher/JVMGopher.scala | 6 +- .../gopher/impl/GuardedSPSCBaseChannel.scala | 131 ++++++++++++++ .../impl/GuardedSPSCBufferedChannel.scala | 169 ++++++++++++++++++ .../impl/GuardedSPSCUnbufferedChannel.scala | 67 +++++++ .../gopher/impl/MTUnbufferedChannel.scala | 90 ---------- .../main/scala/gopher/impl/SPSCBuffer.scala | 27 +++ .../test/scala/gopher/ApiAccessTests.scala | 17 +- shared/src/main/scala/gopher/Channel.scala | 3 +- 10 files changed, 421 insertions(+), 104 deletions(-) create mode 100644 jvm/src/main/scala/gopher/impl/GuardedSPSCBaseChannel.scala create mode 100644 jvm/src/main/scala/gopher/impl/GuardedSPSCBufferedChannel.scala create mode 100644 jvm/src/main/scala/gopher/impl/GuardedSPSCUnbufferedChannel.scala delete mode 100644 jvm/src/main/scala/gopher/impl/MTUnbufferedChannel.scala create mode 100644 jvm/src/main/scala/gopher/impl/SPSCBuffer.scala diff --git a/build.sbt b/build.sbt index 8e6c98cf..f650225b 100644 --- a/build.sbt +++ b/build.sbt @@ -11,9 +11,6 @@ val sharedSettings = Seq( name := "scala-gopher", resolvers += "Local Ivy Repository" at "file://"+Path.userHome.absolutePath+"/.ivy2/local", libraryDependencies += "com.github.rssh" %%% "dotty-cps-async" % "0.3.4p1-M2-SNAPSHOT", - // 0.7.5 for scaladoc is not exists - //libraryDependencies += "com.lihaoyi" %%% "utest" % "0.7.5" % "test", - //testFrameworks += new TestFramework("utest.runner.Framework") libraryDependencies += "org.scalameta" %%% "munit" % "0.7.19" % Test, testFrameworks += new TestFramework("munit.Framework") ) diff --git a/js/src/main/scala/gopher/impl/BaseChannel.scala b/js/src/main/scala/gopher/impl/BaseChannel.scala index c98def5a..3bf26375 100644 --- a/js/src/main/scala/gopher/impl/BaseChannel.scala +++ b/js/src/main/scala/gopher/impl/BaseChannel.scala @@ -20,6 +20,8 @@ abstract class BaseChannel[F[_]:CpsAsyncMonad,A](root: JSGopher[F]) extends Chan protected def internalDequeueFinish(): Unit protected def internalEnqueue(a:A): Boolean + override def close(): Unit = + closed = true protected def submitTask(f: ()=>Unit ): Unit = JSExecutionContext.queue.execute{ () => @@ -34,7 +36,7 @@ abstract class BaseChannel[F[_]:CpsAsyncMonad,A](root: JSGopher[F]) extends Chan } def addReader(reader: Reader[A]): Unit = - if (closed) { + if (closed && internalDequeuePeek().isEmpty ) { reader.capture().foreach{ f => submitTask( () => f(Failure(new ChannelClosedException())) @@ -63,9 +65,11 @@ abstract class BaseChannel[F[_]:CpsAsyncMonad,A](root: JSGopher[F]) extends Chan var progress = true while(progress) internalDequeuePeek() match - case Some(a) => progress = processReaders(a) - case None => progress = processWriters() - + case Some(a) => + progress = processReaders(a) + case None => + progress = processWriters() + protected def processReaders(a:A): Boolean = var progress = false if (!readers.isEmpty) then diff --git a/jvm/src/main/scala/gopher/JVMGopher.scala b/jvm/src/main/scala/gopher/JVMGopher.scala index 40dd97b9..48d6ad1f 100644 --- a/jvm/src/main/scala/gopher/JVMGopher.scala +++ b/jvm/src/main/scala/gopher/JVMGopher.scala @@ -13,10 +13,10 @@ class JVMGopher[F[_]:CpsSchedulingMonad](cfg: JVMGopherConfig) extends Gopher[F] def makeChannel[A](bufSize:Int) = - if (bufSize == 1) - MTUnbufferedChannel[F,A](cfg.controlExecutor,cfg.taskExecutor) + if (bufSize == 0) + GuardedSPSCUnbufferedChannel[F,A](this, cfg.controlExecutor,cfg.taskExecutor) else - ??? + GuardedSPSCBufferedChannel[F,A](this, bufSize, cfg.controlExecutor,cfg.taskExecutor) object JVMGopher extends GopherAPI: diff --git a/jvm/src/main/scala/gopher/impl/GuardedSPSCBaseChannel.scala b/jvm/src/main/scala/gopher/impl/GuardedSPSCBaseChannel.scala new file mode 100644 index 00000000..68e68f76 --- /dev/null +++ b/jvm/src/main/scala/gopher/impl/GuardedSPSCBaseChannel.scala @@ -0,0 +1,131 @@ +package gopher.impl + +import cps._ +import gopher._ +import java.util.concurrent.Executor +import java.util.concurrent.ExecutorService +import java.util.concurrent.ConcurrentLinkedDeque +import java.util.concurrent.atomic.AtomicBoolean +import java.util.concurrent.atomic.AtomicInteger +import java.util.concurrent.atomic.AtomicReference +import scala.util.Try +import scala.util.Success +import scala.util.Failure + + +/** + * Guarded channel work in the next way: + * reader and writer asynchronically added to readers and writers and force evaluation of internal step function + * or ensure that currently running step function will see the chanes in readers/writers. + * Step functions is executed in some thread loop, and in the same time, only one instance of step function is running. + * (which is ensured by guard) + **/ +abstract class GuardedSPSCBaseChannel[F[_]:CpsAsyncMonad,A](gopherApi: JVMGopher[F], controlExecutor: ExecutorService, taskExecutor: ExecutorService) extends Channel[F,A,A]: + + import GuardedSPSCBaseChannel._ + + protected val readers = new ConcurrentLinkedDeque[Reader[A]]() + protected val writers = new ConcurrentLinkedDeque[Writer[A]]() + + protected val publishedClosed = new AtomicBoolean(false) + + protected val stepGuard = new AtomicInteger(STEP_FREE) + + override protected def asyncMonad: CpsAsyncMonad[F] = summon[CpsAsyncMonad[F]] + + protected val stepRunnable: Runnable = (()=>entryStep()) + + + def addReader(reader: Reader[A]): Unit = + if (reader.canExpire) then + readers.removeIf( _.isExpired ) + readers.add(reader) + controlExecutor.submit(stepRunnable) + + def addWriter(writer: Writer[A]): Unit = + if (writer.canExpire) then + writers.removeIf( _.isExpired ) + writers.add(writer) + controlExecutor.submit(stepRunnable) + + def close(): Unit = + publishedClosed.set(true) + controlExecutor.submit(stepRunnable) + + protected def step(): Unit + + + protected def entryStep(): Unit = + var done = false + while(!done) { + if (stepGuard.compareAndSet(STEP_FREE,STEP_BUSY)) { + done = true + step() + } else if (stepGuard.compareAndSet(STEP_BUSY, STEP_UPDATED)) { + done = true + } else if (stepGuard.get() == STEP_UPDATED) { + // merge with othwer changes + done = true + } else { + // other set updates, we should spinLock + Thread.onSpinWait() + } + } + + /** + * if truw - we can leave step, otherwise better run yet one step. + */ + protected def checkLeaveStep(): Boolean = + if (stepGuard.compareAndSet(STEP_BUSY,STEP_FREE)) then + true + else if (stepGuard.compareAndSet(STEP_UPDATED, STEP_BUSY)) then + false + else + // impossible, let'a r + false + + + protected def processReadClose(): Boolean = + var progress = false + while(!readers.isEmpty) { + val r = readers.poll() + if (!(r eq null) && !r.isExpired) then + r.capture() match + case Some(f) => + progress = true + taskExecutor.execute(() => f(Failure(new ChannelClosedException())) ) + case None => + progress = true + Thread.onSpinWait() + if (!r.isExpired ) then + readers.addLast(r) + } + progress + + protected def processWriteClose(): Boolean = + var progress = false + while(!writers.isEmpty) { + val w = writers.poll() + if !(w eq null) && !w.isExpired then + w.capture() match + case Some((a,f)) => + progress = true + taskExecutor.execute(() => f(Failure(new ChannelClosedException)) ) + case None => + progress = true + if (!w.isExpired) then + writers.addLast(w) + } + progress + + +object GuardedSPSCBaseChannel: + + final val STEP_FREE = 0 + + final val STEP_BUSY = 1 + + final val STEP_UPDATED = 2 + + + diff --git a/jvm/src/main/scala/gopher/impl/GuardedSPSCBufferedChannel.scala b/jvm/src/main/scala/gopher/impl/GuardedSPSCBufferedChannel.scala new file mode 100644 index 00000000..1737e3bb --- /dev/null +++ b/jvm/src/main/scala/gopher/impl/GuardedSPSCBufferedChannel.scala @@ -0,0 +1,169 @@ +package gopher.impl + +import cps._ +import gopher._ +import java.util.concurrent.ExecutorService +import java.util.concurrent.atomic.AtomicReferenceArray +import java.util.concurrent.atomic.AtomicInteger +import scala.util.Try +import scala.util.Success +import scala.util.Failure + + +class GuardedSPSCBufferedChannel[F[_]:CpsAsyncMonad,A](gopherApi: JVMGopher[F], bufSize: Int, +controlExecutor: ExecutorService, +taskExecutor: ExecutorService) extends GuardedSPSCBaseChannel[F,A](gopherApi,controlExecutor, taskExecutor): + + import GuardedSPSCBaseChannel._ + + class RingBuffer extends SPSCBuffer[A] { + + val refs: AtomicReferenceArray[AnyRef | Null] = new AtomicReferenceArray(bufSize); + val publishedStart: AtomicInteger = new AtomicInteger(0) + val publishedSize: AtomicInteger = new AtomicInteger(0) + + var start: Int = 0 + var size: Int = 0 + + override def local(): Unit = { + start = publishedStart.get() + size = publishedSize.get() + } + + override def publish(): Unit = { + publishedStart.set(start) + publishedSize.set(size) + } + + override def isEmpty(): Boolean = (size == 0) + override def isFull(): Boolean = (size == bufSize) + + override def startRead(): A = { + val aRef = refs.get(start) + //TODO: enable debug mode + //if (aRef eq null) { + // throw new IllegalStateException("read null item") + //} + aRef.nn.asInstanceOf[A] + } + + override def finishRead(): Boolean = { + if (size > 0) then + start = (start + 1) % bufSize + size = size - 1 + true + else + false + } + + override def write(a:A): Boolean = { + if (size < bufSize) then + val end = (start + size) % bufSize + val aRef: AnyRef | Null = a.asInstanceOf[AnyRef] // boxing + refs.lazySet(end,aRef) + size += 1 + true + else + false + } + + } + + //Mutable buffer state + protected val state: SPSCBuffer[A] = new RingBuffer() + + + protected def step(): Unit = + state.local() + var isClosed = publishedClosed.get() + var progress = true + while(progress) { + progress = false + if !state.isEmpty() then + val a = state.startRead() + progress |= processReadsStep() + else + if isClosed then + progress |= processReadClose() + if (!state.isFull()) then + progress |= processWriteStep() + if (isClosed) + progress |= processWriteClose() + if (!progress) { + state.publish() + if (! checkLeaveStep()) { + progress = true + } + } + } + + + + private def processReadsStep(): Boolean = + // precondition: !isEmpty + val a = state.startRead() + var done = false + var progress = false + var nonExpiredBusyReads = scala.collection.immutable.Queue.empty[Reader[A]] + while(!done && !readers.isEmpty) { + val reader = readers.poll() + if !(reader eq null) && !reader.isExpired then + reader.capture() match + case Some(f) => + // try/cath arround f is a reader reponsability + taskExecutor.execute(() => f(Success(a))) + reader.markUsed() + state.finishRead() + progress = true + done = true + case None => + if !reader.isExpired then + nonExpiredBusyReads = nonExpiredBusyReads.enqueue(reader) + } + while(nonExpiredBusyReads.nonEmpty) { + // not in this thread, but progress. + progress = true + val (r, c) = nonExpiredBusyReads.dequeue + if (!r.isExpired) { + readers.addLast(r) + //TOOD: check lack of other progress ?? + //Thread.onSpinWait() + } + nonExpiredBusyReads = c + } + progress + + // precondition: ! isFUll + private def processWriteStep(): Boolean = + var progress = false + var done = false + var nonExpiredBusyWriters = scala.collection.immutable.Queue.empty[Writer[A]] + while(!done && !writers.isEmpty) { + val writer = writers.poll() + if !(writer eq null ) && ! writer.isExpired then + writer.capture() match + case Some((a,f)) => + done = true + if (state.write(a)) then + taskExecutor.execute(() => f(Success(()))) + progress = true + else + // impossible, because state + //TODO: log + println("impossibe,unsuccesfull write after !isFull") + writers.addFirst(writer) + case None => + if (!writer.isExpired) + nonExpiredBusyWriters = nonExpiredBusyWriters.enqueue(writer) + } + while(nonExpiredBusyWriters.nonEmpty) { + progress = true + val (w, c) = nonExpiredBusyWriters.dequeue + nonExpiredBusyWriters = c + if (! w.isExpired) then + writers.addLast(w) + } + progress + + +end GuardedSPSCBufferedChannel diff --git a/jvm/src/main/scala/gopher/impl/GuardedSPSCUnbufferedChannel.scala b/jvm/src/main/scala/gopher/impl/GuardedSPSCUnbufferedChannel.scala new file mode 100644 index 00000000..05ffbb68 --- /dev/null +++ b/jvm/src/main/scala/gopher/impl/GuardedSPSCUnbufferedChannel.scala @@ -0,0 +1,67 @@ +package gopher.impl + +import cps._ +import gopher._ + +import java.util.concurrent.ExecutorService +import java.util.concurrent.atomic.AtomicReferenceArray +import java.util.concurrent.atomic.AtomicInteger +import scala.util.Try +import scala.util.Success +import scala.util.Failure + +import GuardedSPSCBaseChannel._ + + +class GuardedSPSCUnbufferedChannel[F[_]:CpsAsyncMonad,A]( + gopherApi: JVMGopher[F], + controlExecutor: ExecutorService, + taskExecutor: ExecutorService) extends GuardedSPSCBaseChannel[F,A](gopherApi,controlExecutor, taskExecutor): + + protected override def step(): Unit = { + var progress = true + while (progress) { + var readerLoopDone = false + progress = false + while(!readerLoopDone && !readers.isEmpty && !writers.isEmpty) { + val reader = readers.poll() + if (!(reader eq null) && !reader.isExpired) then + var writersLoopDone = false + while(! writersLoopDone && !readerLoopDone && !writers.isEmpty) { + var writer = writers.poll() + if (!(writer eq null) && !writer.isExpired) then + // now we have reader and writer + reader.capture() match + case Some(readFun) => + writer.capture() match + case Some((a,writeFun)) => + // great, now we have all + taskExecutor.execute(()=>readFun(Success(a))) + taskExecutor.execute(()=>writeFun(Success(()))) + progress = true + reader.markUsed() + writer.markUsed() + writersLoopDone = true + case None => + // reader same, other writer + reader.markFree() + progress = true // return when + if (!writer.isExpired) + writers.addLast(writer) + Thread.onSpinWait() + case None => + writers.addFirst(writer) + writersLoopDone = true + progress = true + if (!reader.isExpired) + readers.addLast(reader) + Thread.onSpinWait() + } + } + if (!progress) then + if !checkLeaveStep() then + progress = true + } + } + + diff --git a/jvm/src/main/scala/gopher/impl/MTUnbufferedChannel.scala b/jvm/src/main/scala/gopher/impl/MTUnbufferedChannel.scala deleted file mode 100644 index 0ebd29b9..00000000 --- a/jvm/src/main/scala/gopher/impl/MTUnbufferedChannel.scala +++ /dev/null @@ -1,90 +0,0 @@ -package gopher.impl - -import cps._ -import gopher._ - -import java.lang.Runnable -import java.util.concurrent.Executor -import java.util.concurrent.ExecutorService -import java.util.concurrent.ConcurrentLinkedDeque -import java.util.concurrent.atomic.AtomicBoolean -import java.util.concurrent.atomic.AtomicReference -import scala.util.Try -import scala.util.Success -import scala.util.Failure - -class MTUnbufferedChannel[F[_]:CpsAsyncMonad,A](controlExecutor: ExecutorService, taskExecutor: ExecutorService) extends Channel[F,A,A]: - - private val readers = new ConcurrentLinkedDeque[Reader[A]]() - private val writers = new ConcurrentLinkedDeque[Writer[A]]() - private val ref = new AtomicReference[AnyRef|Null](null) - private val isClosed = new AtomicBoolean(false) - private val stepRunnable: Runnable = (()=>step()) - - override protected def asyncMonad: CpsAsyncMonad[F] = summon[CpsAsyncMonad[F]] - - def addReader(reader: Reader[A]): Unit = - if (reader.canExpire) then - readers.removeIf( _.isExpired ) - readers.add(reader) - controlExecutor.submit(stepRunnable) - - def addWriter(writer: Writer[A]): Unit = - if (writer.canExpire) then - writers.removeIf( _.isExpired ) - writers.add(writer) - controlExecutor.submit(stepRunnable) - - - - // called only from control executor - def step(): Unit = - var progress = true - while(progress) - progress = false - val a: AnyRef|Null = ref.get() - if !(a eq null) then - val reader = readers.poll() - if !(reader eq null) then - progress = true - reader.capture() match - case Some(f) => - if (ref.compareAndSet(a,null)) - // TODO: explicit boxing/unboxing ? - taskExecutor.execute( ()=>f(Success(a.nn.asInstanceOf[A])) ) // TODO: what if f throws exception [?] - reader.markUsed() - else - // somebody from other thread stole our 'a', so need to try again - reader.markFree() - readers.addFirst(reader) - case None => - // Here should be next variant: - // this select group is other thread in other channel - // if this call will successfull - reader become expired. - // if not - we should not keep one here. (but if it's bloked - we can put one at the end of the - // tail to try next thing now) - // (when this is one reader - this will create something like spin-lock, when we will have no real progres here, - // but know, that some progress was in the other thread) - if (!reader.isExpired) - if (readers.isEmpty) Thread.`yield`() - readers.addLast(reader) - else - val writer = writers.poll() - if !(writer eq null) then - progress = true - writer.capture() match - case Some((a,f)) => - val ar = a.asInstanceOf[AnyRef] - if (ref.compareAndSet(null,ar)) - taskExecutor.execute( () => f(Success(())) ) - writer.markUsed() - else - // somebody fill our references. - writer.markFree() - writers.addFirst(writer) - case None => - if (!writer.isExpired) - if (writers.isEmpty) Thread.`yield`() - writers.addLast(writer) - - diff --git a/jvm/src/main/scala/gopher/impl/SPSCBuffer.scala b/jvm/src/main/scala/gopher/impl/SPSCBuffer.scala new file mode 100644 index 00000000..1112fa1d --- /dev/null +++ b/jvm/src/main/scala/gopher/impl/SPSCBuffer.scala @@ -0,0 +1,27 @@ +package gopher.impl + +import gopher._ + +/** +* Buffer. access to buffer is exclusive by owner channel, +* different loops can start in different threads but only one loop can be active at the samw time +**/ +trait SPSCBuffer[A] { + + def isEmpty(): Boolean + + def startRead(): A + def finishRead(): Boolean + + def isFull(): Boolean + // prcondition: !isFull() + def write(a: A): Boolean + + + // set local state from published + def local(): Unit + + // make buffer be readable from other thread than + def publish(): Unit + +} \ No newline at end of file diff --git a/jvm/src/test/scala/gopher/ApiAccessTests.scala b/jvm/src/test/scala/gopher/ApiAccessTests.scala index 350ef800..f5c52fa0 100644 --- a/jvm/src/test/scala/gopher/ApiAccessTests.scala +++ b/jvm/src/test/scala/gopher/ApiAccessTests.scala @@ -7,14 +7,25 @@ import cps._ import cps.monads.FutureAsyncMonad import scala.language.postfixOps +import munit._ - -class ApiAccessTests extends munit.FunSuite { +class ApiAccessTests extends FunSuite { import scala.concurrent.ExecutionContext.Implicits.global - test("simple channel") { + test("simple unbuffered channel") { + given Gopher[Future] = JVMGopher[Future]() + val ch = makeChannel[Int](0) + val fw1 = ch.awrite(1) + //println("after awrite") + val fr1 = ch.aread + //println("after aread, waiting result") + val r1 = Await.result(fr1, 1 second) + assert( r1 == 1 ) + } + + test("simple 1-buffered channel") { given Gopher[Future] = JVMGopher[Future]() val ch = makeChannel[Int](1) val fw1 = ch.awrite(1) diff --git a/shared/src/main/scala/gopher/Channel.scala b/shared/src/main/scala/gopher/Channel.scala index 402ecb46..28773dad 100644 --- a/shared/src/main/scala/gopher/Channel.scala +++ b/shared/src/main/scala/gopher/Channel.scala @@ -1,8 +1,9 @@ package gopher import cps._ +import java.io.Closeable -trait Channel[F[_],W,R] extends WriteChannel[F,W] with ReadChannel[F,R]: +trait Channel[F[_],W,R] extends WriteChannel[F,W] with ReadChannel[F,R] with Closeable: override protected def asyncMonad: CpsAsyncMonad[F] From 38f5e502e69fd4817aad4c5d20728f8cb7695e08 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Mon, 7 Dec 2020 15:00:47 +0200 Subject: [PATCH 006/161] implemented basic channels --- .../scala/gopher/hofasyn/HofAsyncSuite.scala | 1 + .../main/scala/gopher/impl/BaseChannel.scala | 36 +-------- .../scala/gopher/impl/BufferedChannel.scala | 79 ++++++++++++------- .../scala/gopher/impl/UnbufferedChannel.scala | 55 ++++++++++--- shared/src/main/scala/gopher/Channel.scala | 1 - shared/src/main/scala/gopher/Gopher.scala | 5 +- .../src/main/scala/gopher/ReadChannel.scala | 1 + shared/src/main/scala/gopher/Select.scala | 19 +++++ .../src/main/scala/gopher/SelectGroup.scala | 42 +++++++++- .../src/main/scala/gopher/WriteChannel.scala | 14 +++- .../scala/gopher/{ => impl}/Expirable.scala | 2 +- .../main/scala/gopher/{ => impl}/Reader.scala | 3 +- .../main/scala/gopher/{ => impl}/Writer.scala | 2 +- .../gopher/channels/AsyncChannelTests.scala | 37 +++++++++ .../channels/FibonnachySimpleTests.scala | 36 +++++++++ 15 files changed, 249 insertions(+), 84 deletions(-) create mode 100644 shared/src/main/scala/gopher/Select.scala rename shared/src/main/scala/gopher/{ => impl}/Expirable.scala (97%) rename shared/src/main/scala/gopher/{ => impl}/Reader.scala (72%) rename shared/src/main/scala/gopher/{ => impl}/Writer.scala (80%) create mode 100644 shared/src/test/scala/gopher/channels/AsyncChannelTests.scala create mode 100644 shared/src/test/scala/gopher/channels/FibonnachySimpleTests.scala diff --git a/0.99.x/src/test/scala/gopher/hofasyn/HofAsyncSuite.scala b/0.99.x/src/test/scala/gopher/hofasyn/HofAsyncSuite.scala index 06fd285e..e02ebc24 100644 --- a/0.99.x/src/test/scala/gopher/hofasyn/HofAsyncSuite.scala +++ b/0.99.x/src/test/scala/gopher/hofasyn/HofAsyncSuite.scala @@ -47,6 +47,7 @@ class HofAsyncSuite extends FunSuite } + // Moved to new test("test async operations inside map") { val channel = gopherApi.makeChannel[Int](100) channel.awriteAll(1 to 100) diff --git a/js/src/main/scala/gopher/impl/BaseChannel.scala b/js/src/main/scala/gopher/impl/BaseChannel.scala index 3bf26375..a19d7837 100644 --- a/js/src/main/scala/gopher/impl/BaseChannel.scala +++ b/js/src/main/scala/gopher/impl/BaseChannel.scala @@ -16,9 +16,7 @@ abstract class BaseChannel[F[_]:CpsAsyncMonad,A](root: JSGopher[F]) extends Chan protected override def asyncMonad: cps.CpsAsyncMonad[F] = summon[CpsAsyncMonad[F]] - protected def internalDequeuePeek(): Option[A] - protected def internalDequeueFinish(): Unit - protected def internalEnqueue(a:A): Boolean + override def close(): Unit = closed = true @@ -36,7 +34,7 @@ abstract class BaseChannel[F[_]:CpsAsyncMonad,A](root: JSGopher[F]) extends Chan } def addReader(reader: Reader[A]): Unit = - if (closed && internalDequeuePeek().isEmpty ) { + if (closed && writers.isEmpty ) { reader.capture().foreach{ f => submitTask( () => f(Failure(new ChannelClosedException())) @@ -59,34 +57,6 @@ abstract class BaseChannel[F[_]:CpsAsyncMonad,A](root: JSGopher[F]) extends Chan process() } + protected def process(): Unit - private def process(): Unit = - var progress = true - while(progress) - internalDequeuePeek() match - case Some(a) => - progress = processReaders(a) - case None => - progress = processWriters() - - protected def processReaders(a:A): Boolean = - var progress = false - if (!readers.isEmpty) then - val reader = readers.dequeue() - progress = true - reader.capture().foreach{ f => - internalDequeueFinish() - submitTask( () => f(Success(a)) ) - } - progress - - protected def processWriters(): Boolean = - var progress = false - if (!writers.isEmpty) then - val writer = writers.dequeue() - writer.capture().foreach{ case (a,f) => - internalEnqueue(a) - f(Success(())) - } - progress \ No newline at end of file diff --git a/js/src/main/scala/gopher/impl/BufferedChannel.scala b/js/src/main/scala/gopher/impl/BufferedChannel.scala index 02567674..3aabcd40 100644 --- a/js/src/main/scala/gopher/impl/BufferedChannel.scala +++ b/js/src/main/scala/gopher/impl/BufferedChannel.scala @@ -10,51 +10,70 @@ import scala.util.control.NonFatal class BufferedChannel[F[_]:CpsAsyncMonad, A](gopherApi: JSGopher[F], bufSize: Int) extends BaseChannel[F,A](gopherApi): - val ringBuffer: js.Array[A] = new js.Array[A](bufSize+1) + val ringBuffer: js.Array[A] = new js.Array[A](bufSize) var start: Int = 0 - var end: Int = 0 + var size: Int = 0 // [1] [2] [3] // ˆ ˆ - def isEmpty = (start == end) + def isEmpty = (size == 0) - def nElements = if (end > start) then { - end - start - } else if (start < end) then { - bufSize - start + end - } else 0 + def nElements = size - def isFull = (nElements == bufSize) + def isFull = (size == bufSize) - protected override def internalDequeuePeek(): Option[A] = + protected def internalDequeuePeek(): Option[A] = if isEmpty then None else Some(ringBuffer(start)) - protected override def internalDequeueFinish(): Unit = + protected def internalDequeueFinish(): Unit = + require(size > 0) start = (start + 1) % bufSize + size = size - 1 protected def internalEnqueue(a:A): Boolean = - if (start < end) then - if (end <= bufSize) then - ringBuffer(end) = a - end = end + 1 - true - else if (start == 0) then - false - else - ringBuffer(end) = a - end = 0 - true - else - if (end < start - 1) then - ringBuffer(end) = a - end = end + 1 - true - else - false + if size < bufSize then + val end = (start + size) % bufSize + ringBuffer(end) = a + size = size + 1 + true + else + false + - + protected def process(): Unit = + var progress = true + while(progress) + progress = false + internalDequeuePeek() match + case Some(a) => + progress |= processReaders(a) + case None => + // nothing. + progress |= processWriters() + + protected def processReaders(a:A): Boolean = + var progress = false + if (!readers.isEmpty && !isEmpty) then + val reader = readers.dequeue() + progress = true + reader.capture().foreach{ f => + internalDequeueFinish() + submitTask( () => f(Success(a)) ) + } + progress + + protected def processWriters(): Boolean = + var progress = false + if (!writers.isEmpty && !isFull) then + val writer = writers.dequeue() + writer.capture().foreach{ case (a,f) => + internalEnqueue(a) + f(Success(())) + } + progress + diff --git a/js/src/main/scala/gopher/impl/UnbufferedChannel.scala b/js/src/main/scala/gopher/impl/UnbufferedChannel.scala index 9605ac36..25072ae4 100644 --- a/js/src/main/scala/gopher/impl/UnbufferedChannel.scala +++ b/js/src/main/scala/gopher/impl/UnbufferedChannel.scala @@ -10,17 +10,52 @@ import scala.util.control.NonFatal class UnbufferedChannel[F[_]:CpsAsyncMonad, A](gopherApi: JSGopher[F]) extends BaseChannel[F,A](gopherApi): - private var value: Option[A] = None + protected def process(): Unit = + var progress = true + while(progress) { + progress = false + var done = false + while(!done && !readers.isEmpty && !writers.isEmpty) { + findReader() match + case Some(reader) => + findWriter() match + case Some(writer) => + reader.capture() match + case Some(readFun) => + writer.capture() match + case Some((a,writeFun)) => + submitTask( () => readFun(Success(a))) + submitTask( () => writeFun(Success(())) ) + progress = true + done = true + case None => + // impossible, because in js we have-no interleavinf, bug anyway + // let's fallback + readers.prepend(reader) + case None => + // impossible, but let's fallback + writers.prepend(writer) + case None => + done = true + case None => + done = true + } + } - protected override def internalDequeuePeek(): Option[A] = value - - protected override def internalDequeueFinish(): Unit = - value = None - protected def internalEnqueue(a:A): Boolean = - value match - case None => value = Some(a) - true - case Some(a1) => false + private def findUnexpired[T <: Expirable[?]](q: Queue[T]): Option[T] = + var retval: Option[T] = None + while(retval.isEmpty && ! q.isEmpty) { + val c = q.dequeue; + if (!c.isExpired) { + retval = Some(c) + } + } + retval + private def findReader(): Option[Reader[A]] = + findUnexpired(readers) + private def findWriter(): Option[Writer[A]] = + findUnexpired(writers) + \ No newline at end of file diff --git a/shared/src/main/scala/gopher/Channel.scala b/shared/src/main/scala/gopher/Channel.scala index 28773dad..731d218f 100644 --- a/shared/src/main/scala/gopher/Channel.scala +++ b/shared/src/main/scala/gopher/Channel.scala @@ -9,6 +9,5 @@ trait Channel[F[_],W,R] extends WriteChannel[F,W] with ReadChannel[F,R] with Clo //protected override def asyncMonad: CpsAsyncMonad[F] - end Channel diff --git a/shared/src/main/scala/gopher/Gopher.scala b/shared/src/main/scala/gopher/Gopher.scala index 37a52884..d709876a 100644 --- a/shared/src/main/scala/gopher/Gopher.scala +++ b/shared/src/main/scala/gopher/Gopher.scala @@ -6,7 +6,10 @@ trait Gopher[F[_]:CpsSchedulingMonad]: type Monad[X] = F[X] - def makeChannel[A](bufSize:Int = 1): Channel[F,A,A] + def makeChannel[A](bufSize:Int = 0): Channel[F,A,A] + + def select: Select[F] = + new Select[F](this) def makeChannel[A](bufSize:Int = 1)(using g:Gopher[?]):Channel[g.Monad,A,A] = diff --git a/shared/src/main/scala/gopher/ReadChannel.scala b/shared/src/main/scala/gopher/ReadChannel.scala index 739412f5..0614804c 100644 --- a/shared/src/main/scala/gopher/ReadChannel.scala +++ b/shared/src/main/scala/gopher/ReadChannel.scala @@ -1,6 +1,7 @@ package gopher import cps._ +import gopher.impl._ import scala.util.Try import scala.util.Success import scala.util.Failure diff --git a/shared/src/main/scala/gopher/Select.scala b/shared/src/main/scala/gopher/Select.scala new file mode 100644 index 00000000..881386d9 --- /dev/null +++ b/shared/src/main/scala/gopher/Select.scala @@ -0,0 +1,19 @@ +package gopher + +import cps._ + +import scala.quoted._ + +class Select[F[_]:CpsSchedulingMonad](api: Gopher[F]): + + inline def apply[A](inline pf: PartialFunction[Any,F[A]]): F[A] = + ${ Select.onceImpl[F,A]('pf) } + +object Select: + + def onceImpl[F[_]:Type, A:Type](pf: Expr[PartialFunction[Any,F[A]]])(using Quotes): Expr[F[A]] = + ??? + + + + \ No newline at end of file diff --git a/shared/src/main/scala/gopher/SelectGroup.scala b/shared/src/main/scala/gopher/SelectGroup.scala index 501c38c7..6f9b2910 100644 --- a/shared/src/main/scala/gopher/SelectGroup.scala +++ b/shared/src/main/scala/gopher/SelectGroup.scala @@ -1,12 +1,17 @@ package gopher import cps._ +import gopher.impl._ import java.util.concurrent.atomic.AtomicInteger -import scala.util.Try +import scala.annotation.unchecked.uncheckedVariance +import scala.util._ - -trait SelectGroup[F[_]:CpsSchedulingMonad, S]: +/** + * Select group is a virtual 'lock' object, where only + * ne fro rieader and writer can exists at the sae time. + **/ +class SelectGroup[F[_]:CpsSchedulingMonad, S]: /** * instance of select group created for call of select. @@ -16,7 +21,6 @@ trait SelectGroup[F[_]:CpsSchedulingMonad, S]: private inline def m = summon[CpsSchedulingMonad[F]] val retval = m.adoptCallbackStyle[S](f => call=f) - def addReader[A](ch: ReadChannel[F,A], action: Try[A]=>F[S]): Unit = val record = ReaderRecord(ch, action) ch.addReader(record) @@ -28,6 +32,36 @@ trait SelectGroup[F[_]:CpsSchedulingMonad, S]: def step():F[S] = retval + + /** + * FluentDSL for user SelectGroup without macroses. + *``` + * SelectGroup.reading(input){ x => println(x) } + * .reading(endSignal){ () => done=true } + *``` + **/ + def reading[A](ch: ReadChannel[F,A]) (f: A => S ): this.type = + addReader[A](ch,{ + case Success(a) => m.tryPure(f(a)) + case Failure(ex) => m.error(ex) + }) + this + + // reading call will be tranformed to reader_async in async expressions + def reading_async[A](ch: ReadChannel[F,A]) (f: A => F[S] ): F[this.type] = + addReader[A](ch,{ + case Success(a) => m.tryImpure(f(a)) + case Failure(ex) => m.error(ex) + }) + m.pure(this) + + + // TODO: version with flow termination + //def reading[A](ch: ReadChannel[F,A])( flowTermination ?=> A => F[S] ) + + // + + trait Expiration: def canExpire: Boolean = true diff --git a/shared/src/main/scala/gopher/WriteChannel.scala b/shared/src/main/scala/gopher/WriteChannel.scala index e0e6b8e2..ad68fe30 100644 --- a/shared/src/main/scala/gopher/WriteChannel.scala +++ b/shared/src/main/scala/gopher/WriteChannel.scala @@ -1,6 +1,7 @@ package gopher import cps._ +import gopher.impl._ import scala.util.Try @@ -17,7 +18,18 @@ trait WriteChannel[F[_], A]: def addWriter(writer: Writer[A]): Unit - + def awriteAll(collection: IterableOnce[A]): F[Unit] = + inline given CpsAsyncMonad[F] = asyncMonad + async[F]{ + val it = collection.iterator + while(it.hasNext) { + write(it.next()) + } + } + + inline def writeAll(collection: IterableOnce[A]): Unit = + await(awriteAll(collection))(using asyncMonad) + class SimpleWriter(a:A, f: Try[Unit]=>Unit) extends Writer[A]: def canExpire: Boolean = false diff --git a/shared/src/main/scala/gopher/Expirable.scala b/shared/src/main/scala/gopher/impl/Expirable.scala similarity index 97% rename from shared/src/main/scala/gopher/Expirable.scala rename to shared/src/main/scala/gopher/impl/Expirable.scala index bea508ad..4bad10c4 100644 --- a/shared/src/main/scala/gopher/Expirable.scala +++ b/shared/src/main/scala/gopher/impl/Expirable.scala @@ -1,4 +1,4 @@ -package gopher +package gopher.impl import cps._ diff --git a/shared/src/main/scala/gopher/Reader.scala b/shared/src/main/scala/gopher/impl/Reader.scala similarity index 72% rename from shared/src/main/scala/gopher/Reader.scala rename to shared/src/main/scala/gopher/impl/Reader.scala index 3a3fd312..48552877 100644 --- a/shared/src/main/scala/gopher/Reader.scala +++ b/shared/src/main/scala/gopher/impl/Reader.scala @@ -1,7 +1,6 @@ -package gopher +package gopher.impl import scala.util.Try -import cps._ trait Reader[A] extends Expirable[Try[A]=>Unit] diff --git a/shared/src/main/scala/gopher/Writer.scala b/shared/src/main/scala/gopher/impl/Writer.scala similarity index 80% rename from shared/src/main/scala/gopher/Writer.scala rename to shared/src/main/scala/gopher/impl/Writer.scala index a9db6a3a..132ead65 100644 --- a/shared/src/main/scala/gopher/Writer.scala +++ b/shared/src/main/scala/gopher/impl/Writer.scala @@ -1,4 +1,4 @@ -package gopher +package gopher.impl import scala.util.Try diff --git a/shared/src/test/scala/gopher/channels/AsyncChannelTests.scala b/shared/src/test/scala/gopher/channels/AsyncChannelTests.scala new file mode 100644 index 00000000..636ab308 --- /dev/null +++ b/shared/src/test/scala/gopher/channels/AsyncChannelTests.scala @@ -0,0 +1,37 @@ +package gopher.channels + +import cps._ +import cps.monads.FutureAsyncMonad +import scala.concurrent._ +import scala.concurrent.ExecutionContext.Implicits.global +import gopher._ + +import munit._ + +class AsyncChannelTests extends FunSuite { + + + val gopherApi = SharedGopherAPI[Future]() + val MAX_N = 100 + + test("async base: channel write, channel read") { + + val channel = gopherApi.makeChannel[Int](10) + channel.awriteAll(1 to MAX_N) + + val consumer = async{ + var sum = 0 + while{val a = channel.read + sum += a + a < MAX_N + } do () + sum + } + + consumer.map{ s => + assert(s == (1 to MAX_N).sum) + } + + } + +} \ No newline at end of file diff --git a/shared/src/test/scala/gopher/channels/FibonnachySimpleTests.scala b/shared/src/test/scala/gopher/channels/FibonnachySimpleTests.scala new file mode 100644 index 00000000..82d51a62 --- /dev/null +++ b/shared/src/test/scala/gopher/channels/FibonnachySimpleTests.scala @@ -0,0 +1,36 @@ +package gopher.channels + +import gopher._ + +import munit._ + +/* TODO: enablle after implementing select macroses. +class FibbonachySimpleTest extends FunSuite { + + + import scala.concurrent.ExecutionContext.Implicits.global + val gopherApi = SharedGopherAPI.apply[Future]() + + + def fibonaccy0(c: WriteChannel[Future,Long], quit: ReadChannel[Future,Int]): Future[Unit] = + async[Future]{ + var (x,y) = (0,1) + var done = false + while(!done) { + select{ + case z: c.Write if (z == x) => + x = y + y = z + y + case q: quit.Read => + done = true + } + } + } + + test("simple fibonnachy fun") { + + } + + +} +*/ From 8455560b6d682296395db8d5f169e22214e47c50 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Tue, 8 Dec 2020 09:41:05 +0200 Subject: [PATCH 007/161] added flow-termination handling --- .../main/scala/gopher/FlowTermination.scala | 30 +++++++++++++++++++ shared/src/main/scala/gopher/Select.scala | 22 +++++++++++++- .../src/main/scala/gopher/SelectGroup.scala | 12 ++++---- 3 files changed, 57 insertions(+), 7 deletions(-) create mode 100644 shared/src/main/scala/gopher/FlowTermination.scala diff --git a/shared/src/main/scala/gopher/FlowTermination.scala b/shared/src/main/scala/gopher/FlowTermination.scala new file mode 100644 index 00000000..41f01ed5 --- /dev/null +++ b/shared/src/main/scala/gopher/FlowTermination.scala @@ -0,0 +1,30 @@ +package gopher + +import scala.annotation._ + + +/** + * FlowTermination[-A] - termination of flow. + * + * Inside each block in select loop or + * select apply (once or forever) we have implicit + * FlowTermination entity, which we can use for + * exiting the loop. + * + *{{{ + * select.forever{ + * case x: info.read => Console.println(s"received from info \$x") + * case x: control.read => implicitly[FlowTermination[Unit]].doExit(()) + * } + *}}} + **/ +trait FlowTermination[-A]: + + /** + * terminate current flow and leave `a` as result of flow. + * have no effect if flow is already completed. + */ + def apply(value: A): A@unchecked.uncheckedVariance + + + \ No newline at end of file diff --git a/shared/src/main/scala/gopher/Select.scala b/shared/src/main/scala/gopher/Select.scala index 881386d9..b2b7c0f9 100644 --- a/shared/src/main/scala/gopher/Select.scala +++ b/shared/src/main/scala/gopher/Select.scala @@ -11,9 +11,29 @@ class Select[F[_]:CpsSchedulingMonad](api: Gopher[F]): object Select: + sealed trait SelectorCaseExpr + case class ReadExpression[F[_],A, S](ch: Expr[ReadChannel[F,A]], f: Expr[(FlowTermination[S], A) => F[S]]) + case class WriteExpression[F[_],A, S](ch: Expr[WriteChannel[F,A]], a: Expr[A], f: Expr[FlowTermination[S] => F[S]]) + case class DefaultExpression[F[_],S](ch: Expr[FlowTermination[S] => F[S]]) + + def onceImpl[F[_]:Type, A:Type](pf: Expr[PartialFunction[Any,F[A]]])(using Quotes): Expr[F[A]] = + import quotes.reflect._ + Term.of(pf) match + case Match(scrutinee,cases) => + val caseExprs = cases map(x => parseCaseDef(x)) ??? - + def parseCaseDef(using Quotes)(caseDef: quotes.reflect.CaseDef): SelectorCaseExpr = + import quotes.reflect._ + caseDef.pattern match + case Typed(expr, TypeSelect(ch,"Read")) => + if (caseDef.guard.isDefined) then + report.error("guard in Read should be empty", caseDef.asExpr) + println(s"caseDef.rhs.tpe=${caseDef.rhs.tpe}" ) + ??? + + + \ No newline at end of file diff --git a/shared/src/main/scala/gopher/SelectGroup.scala b/shared/src/main/scala/gopher/SelectGroup.scala index 6f9b2910..44713662 100644 --- a/shared/src/main/scala/gopher/SelectGroup.scala +++ b/shared/src/main/scala/gopher/SelectGroup.scala @@ -11,7 +11,7 @@ import scala.util._ * Select group is a virtual 'lock' object, where only * ne fro rieader and writer can exists at the sae time. **/ -class SelectGroup[F[_]:CpsSchedulingMonad, S]: +class SelectGroup[F[_]:CpsSchedulingMonad, S](using FlowTermination[S]): /** * instance of select group created for call of select. @@ -20,12 +20,12 @@ class SelectGroup[F[_]:CpsSchedulingMonad, S]: var call: Try[S] => Unit = { _ => () } private inline def m = summon[CpsSchedulingMonad[F]] val retval = m.adoptCallbackStyle[S](f => call=f) - - def addReader[A](ch: ReadChannel[F,A], action: Try[A]=>F[S]): Unit = + + def addReader[A](ch: ReadChannel[F,A], action: FlowTermination[S] ?=> Try[A]=>F[S]): Unit = val record = ReaderRecord(ch, action) ch.addReader(record) - def addWriter[A](ch: WriteChannel[F,A], element: A, action: Try[Unit]=>F[S]): Unit = + def addWriter[A](ch: WriteChannel[F,A], element: A, action: FlowTermination[S] ?=> Try[Unit]=>F[S]): Unit = val record = WriterRecord(ch, element, action) ch.addWriter(record) @@ -69,7 +69,7 @@ class SelectGroup[F[_]:CpsSchedulingMonad, S]: def markUsed(): Unit = waitState.lazySet(2) def markFree(): Unit = waitState.set(0) - case class ReaderRecord[A](ch: ReadChannel[F,A], action: Try[A] => F[S]) extends Reader[A] with Expiration: + case class ReaderRecord[A](ch: ReadChannel[F,A], action: FlowTermination[S] ?=> Try[A] => F[S]) extends Reader[A] with Expiration: type Element = A type State = S @@ -87,7 +87,7 @@ class SelectGroup[F[_]:CpsSchedulingMonad, S]: case class WriterRecord[A](ch: WriteChannel[F,A], element: A, - action: Try[Unit] => F[S], + action: FlowTermination[S] ?=> Try[Unit] => F[S], ) extends Writer[A] with Expiration: type Element = A type State = S From e7a4433065c800fa37795982f46f23f272491666 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Tue, 8 Dec 2020 20:59:52 +0200 Subject: [PATCH 008/161] macro reimplementation in progress --- .../main/scala/gopher/FlowTermination.scala | 13 +++-- shared/src/main/scala/gopher/Gopher.scala | 3 + .../src/main/scala/gopher/ReadChannel.scala | 5 +- shared/src/main/scala/gopher/Select.scala | 44 +++++++++++---- .../src/main/scala/gopher/SelectGroup.scala | 34 +++++++---- .../src/main/scala/gopher/WriteChannel.scala | 6 ++ .../channels/FibonnachySimpleTests.scala | 56 +++++++++++++++++-- 7 files changed, 128 insertions(+), 33 deletions(-) diff --git a/shared/src/main/scala/gopher/FlowTermination.scala b/shared/src/main/scala/gopher/FlowTermination.scala index 41f01ed5..53f2a02f 100644 --- a/shared/src/main/scala/gopher/FlowTermination.scala +++ b/shared/src/main/scala/gopher/FlowTermination.scala @@ -2,9 +2,12 @@ package gopher import scala.annotation._ +sealed trait SelectFlow[S] + + /** - * FlowTermination[-A] - termination of flow. + * FlowTermination- termination of flow. * * Inside each block in select loop or * select apply (once or forever) we have implicit @@ -14,17 +17,17 @@ import scala.annotation._ *{{{ * select.forever{ * case x: info.read => Console.println(s"received from info \$x") - * case x: control.read => implicitly[FlowTermination[Unit]].doExit(()) + * case x: control.read => Select.Done(()) * } *}}} **/ -trait FlowTermination[-A]: +trait FlowTermination: /** * terminate current flow and leave `a` as result of flow. * have no effect if flow is already completed. */ - def apply(value: A): A@unchecked.uncheckedVariance + def apply[A](value: A): A - \ No newline at end of file + diff --git a/shared/src/main/scala/gopher/Gopher.scala b/shared/src/main/scala/gopher/Gopher.scala index d709876a..690defda 100644 --- a/shared/src/main/scala/gopher/Gopher.scala +++ b/shared/src/main/scala/gopher/Gopher.scala @@ -15,3 +15,6 @@ trait Gopher[F[_]:CpsSchedulingMonad]: def makeChannel[A](bufSize:Int = 1)(using g:Gopher[?]):Channel[g.Monad,A,A] = g.makeChannel(bufSize) +def select(using g:Gopher[?]):Select[g.Monad] = + g.select + diff --git a/shared/src/main/scala/gopher/ReadChannel.scala b/shared/src/main/scala/gopher/ReadChannel.scala index 0614804c..a9ac2831 100644 --- a/shared/src/main/scala/gopher/ReadChannel.scala +++ b/shared/src/main/scala/gopher/ReadChannel.scala @@ -8,7 +8,7 @@ import scala.util.Failure trait ReadChannel[F[_], A]: - type Read = A + type read = A // workarround for https://github.com/lampepfl/dotty/issues/10477 protected def asyncMonad: CpsAsyncMonad[F] @@ -25,6 +25,9 @@ trait ReadChannel[F[_], A]: inline def ? : A = await(aread)(using rAsyncMonad) + object Read: + def unapply(): Option[A] = ??? + def aOptRead: F[Option[A]] = asyncMonad.adoptCallbackStyle( f => addReader(SimpleReader{ x => x match diff --git a/shared/src/main/scala/gopher/Select.scala b/shared/src/main/scala/gopher/Select.scala index b2b7c0f9..8aa79d0e 100644 --- a/shared/src/main/scala/gopher/Select.scala +++ b/shared/src/main/scala/gopher/Select.scala @@ -6,20 +6,28 @@ import scala.quoted._ class Select[F[_]:CpsSchedulingMonad](api: Gopher[F]): - inline def apply[A](inline pf: PartialFunction[Any,F[A]]): F[A] = + inline def apply[A](inline pf: PartialFunction[Any,A]): A = ${ Select.onceImpl[F,A]('pf) } object Select: sealed trait SelectorCaseExpr - case class ReadExpression[F[_],A, S](ch: Expr[ReadChannel[F,A]], f: Expr[(FlowTermination[S], A) => F[S]]) - case class WriteExpression[F[_],A, S](ch: Expr[WriteChannel[F,A]], a: Expr[A], f: Expr[FlowTermination[S] => F[S]]) - case class DefaultExpression[F[_],S](ch: Expr[FlowTermination[S] => F[S]]) + case class ReadExpression[F[_],A, S](ch: Expr[ReadChannel[F,A]], f: Expr[(FlowTermination, A) => F[S]]) + case class WriteExpression[F[_],A, S](ch: Expr[WriteChannel[F,A]], a: Expr[A], f: Expr[FlowTermination => F[S]]) + case class DefaultExpression[F[_],S](ch: Expr[FlowTermination => F[S]]) - def onceImpl[F[_]:Type, A:Type](pf: Expr[PartialFunction[Any,F[A]]])(using Quotes): Expr[F[A]] = + def onceImpl[F[_]:Type, A:Type](pf: Expr[PartialFunction[Any,A]])(using Quotes): Expr[A] = import quotes.reflect._ - Term.of(pf) match + onceImplTree[F,A](Term.of(pf)).asExprOf[A] + + def onceImplTree[F[_]:Type, A:Type](using Quotes)(pf: quotes.reflect.Term): quotes.reflect.Term = + import quotes.reflect._ + pf match + case Lambda(valDefs, body) => + onceImplTree[F,A](body) + case Inlined(_,List(),body) => + onceImplTree[F,A](body) case Match(scrutinee,cases) => val caseExprs = cases map(x => parseCaseDef(x)) ??? @@ -27,13 +35,29 @@ object Select: def parseCaseDef(using Quotes)(caseDef: quotes.reflect.CaseDef): SelectorCaseExpr = import quotes.reflect._ caseDef.pattern match - case Typed(expr, TypeSelect(ch,"Read")) => + case Inlined(_,List(),body) => + parseCaseDef(CaseDef(body, caseDef.guard, caseDef.rhs)) + case Typed(expr, TypeSelect(ch,"read")) => if (caseDef.guard.isDefined) then - report.error("guard in Read should be empty", caseDef.asExpr) + report.error("guard in read should be empty", caseDef.asExpr) println(s"caseDef.rhs.tpe=${caseDef.rhs.tpe}" ) + case Bind(v, Typed(expr, TypeSelect(ch,"read"))) => + val paramName = v match + case Ident(x) => x + case _ => + report.error("expected identifier in read pattern", v.asExpr) + "x" + val mt = MethodType(List(paramName))(_ => List(v.tpe), _ => caseDef.rhs.tpe) + val readFun = Lambda(Symbol.spliceOwner,mt, + (owner, args) => substIdent(body,v,args.head).changeOwner(owner)) + ReadExpression(ch.asExpr,readFun) + case Bind(v, Typed(expr, TypeSelect(ch,"write"))) => + println(s"!!! write discovered" ) + case _ => + println(s"unparsed caseDef pattern: ${caseDef.pattern}" ) ??? - + def substIdent(using Quotes)(term: Term, fromTerm: Term, toTerm: Term):Term = ??? - \ No newline at end of file + diff --git a/shared/src/main/scala/gopher/SelectGroup.scala b/shared/src/main/scala/gopher/SelectGroup.scala index 44713662..7652cd5a 100644 --- a/shared/src/main/scala/gopher/SelectGroup.scala +++ b/shared/src/main/scala/gopher/SelectGroup.scala @@ -11,7 +11,7 @@ import scala.util._ * Select group is a virtual 'lock' object, where only * ne fro rieader and writer can exists at the sae time. **/ -class SelectGroup[F[_]:CpsSchedulingMonad, S](using FlowTermination[S]): +class SelectGroup[F[_]:CpsSchedulingMonad, S]: /** * instance of select group created for call of select. @@ -21,17 +21,18 @@ class SelectGroup[F[_]:CpsSchedulingMonad, S](using FlowTermination[S]): private inline def m = summon[CpsSchedulingMonad[F]] val retval = m.adoptCallbackStyle[S](f => call=f) - def addReader[A](ch: ReadChannel[F,A], action: FlowTermination[S] ?=> Try[A]=>F[S]): Unit = + def addReader[A](ch: ReadChannel[F,A], action: Try[A]=>F[S]): Unit = val record = ReaderRecord(ch, action) ch.addReader(record) - def addWriter[A](ch: WriteChannel[F,A], element: A, action: FlowTermination[S] ?=> Try[Unit]=>F[S]): Unit = + def addWriter[A](ch: WriteChannel[F,A], element: A, action: Try[Unit]=>F[S]): Unit = val record = WriterRecord(ch, element, action) ch.addWriter(record) def step():F[S] = retval + inline def run: S = await(step()) /** * FluentDSL for user SelectGroup without macroses. @@ -48,28 +49,38 @@ class SelectGroup[F[_]:CpsSchedulingMonad, S](using FlowTermination[S]): this // reading call will be tranformed to reader_async in async expressions - def reading_async[A](ch: ReadChannel[F,A]) (f: A => F[S] ): F[this.type] = + def reading_async[A](ch: ReadChannel[F,A])(f: A => F[S] ): F[this.type] = addReader[A](ch,{ case Success(a) => m.tryImpure(f(a)) case Failure(ex) => m.error(ex) }) m.pure(this) + def writing[A](ch: WriteChannel[F,A], a:A)(f: =>S ): SelectGroup[F,S] = + addWriter[A](ch,a,{ + case Success(()) => m.tryPure(f) + case Failure(ex) => m.error(ex) + }) + this - // TODO: version with flow termination - //def reading[A](ch: ReadChannel[F,A])( flowTermination ?=> A => F[S] ) - - // + def writing_async[A](ch: WriteChannel[F,A], a:A) (f: ()=> F[S] ): F[this.type] = + addWriter[A](ch,a,{ + case Success(()) => m.tryImpure(f()) + case Failure(ex) => m.error(ex) + }) + m.pure(this) - + // trait Expiration: def canExpire: Boolean = true def isExpired: Boolean = waitState.get()==2 def markUsed(): Unit = waitState.lazySet(2) def markFree(): Unit = waitState.set(0) - case class ReaderRecord[A](ch: ReadChannel[F,A], action: FlowTermination[S] ?=> Try[A] => F[S]) extends Reader[A] with Expiration: + + + case class ReaderRecord[A](ch: ReadChannel[F,A], action: Try[A] => F[S]) extends Reader[A] with Expiration: type Element = A type State = S @@ -87,7 +98,7 @@ class SelectGroup[F[_]:CpsSchedulingMonad, S](using FlowTermination[S]): case class WriterRecord[A](ch: WriteChannel[F,A], element: A, - action: FlowTermination[S] ?=> Try[Unit] => F[S], + action: Try[Unit] => F[S], ) extends Writer[A] with Expiration: type Element = A type State = S @@ -102,3 +113,4 @@ class SelectGroup[F[_]:CpsSchedulingMonad, S](using FlowTermination[S]): + diff --git a/shared/src/main/scala/gopher/WriteChannel.scala b/shared/src/main/scala/gopher/WriteChannel.scala index ad68fe30..32390933 100644 --- a/shared/src/main/scala/gopher/WriteChannel.scala +++ b/shared/src/main/scala/gopher/WriteChannel.scala @@ -4,9 +4,12 @@ import cps._ import gopher.impl._ import scala.util.Try +import scala.annotation.targetName trait WriteChannel[F[_], A]: + type write = A + protected def asyncMonad: CpsAsyncMonad[F] def awrite(a:A):F[Unit] = @@ -16,6 +19,9 @@ trait WriteChannel[F[_], A]: inline def write(a:A): Unit = await(awrite(a))(using asyncMonad) + @targetName("write1") + inline def <~ (a:A): Unit = await(awrite(a))(using asyncMonad) + def addWriter(writer: Writer[A]): Unit def awriteAll(collection: IterableOnce[A]): F[Unit] = diff --git a/shared/src/test/scala/gopher/channels/FibonnachySimpleTests.scala b/shared/src/test/scala/gopher/channels/FibonnachySimpleTests.scala index 82d51a62..43da2d3f 100644 --- a/shared/src/test/scala/gopher/channels/FibonnachySimpleTests.scala +++ b/shared/src/test/scala/gopher/channels/FibonnachySimpleTests.scala @@ -1,36 +1,80 @@ package gopher.channels +import scala.concurrent._ +import cps._ +import cps.monads.FutureAsyncMonad import gopher._ import munit._ -/* TODO: enablle after implementing select macroses. class FibbonachySimpleTest extends FunSuite { import scala.concurrent.ExecutionContext.Implicits.global - val gopherApi = SharedGopherAPI.apply[Future]() + given Gopher[Future] = SharedGopherAPI.apply[Future]() def fibonaccy0(c: WriteChannel[Future,Long], quit: ReadChannel[Future,Int]): Future[Unit] = async[Future]{ var (x,y) = (0,1) var done = false + while(!done) { + // TODO: add select group to given + SelectGroup[Future,Unit]().writing(c, x){ + x=y + y=x+y + } + .reading(quit){ v => + done = true + } + .run + } + } + + def fibonaccy1(c: WriteChannel[Future,Long], quit: ReadChannel[Future,Int]): Future[Unit] = + async[Future]{ + var (x,y) = (0L,1L) + var done = false while(!done) { select{ - case z: c.Write if (z == x) => + case z: c.write if (z == x) => x = y y = z + y - case q: quit.Read => + case q: quit.read => done = true } } } - test("simple fibonnachy fun") { + def run(starter: (WriteChannel[Future,Long], ReadChannel[Future,Int])=> Future[Unit], + acceptor: Long => Unit, n:Int): Future[Unit] = { + val fib = makeChannel[Long]() + val q = makeChannel[Int]() + val start = fibonaccy0(fib, q) + async{ + for( i <- 1 to n) { + var x = fib.read + acceptor(x) + } + q <~ 1 + } + } + + test("simple fibonnachy fun (no macroses)") { + @volatile var last: Long = 0L + async{ + await(run(fibonaccy0, last = _, 50)) + assert(last != 0) + } + } + test("fibonnachy fun (select macros)") { + @volatile var last: Long = 0L + async{ + await(run(fibonaccy0, last = _, 50)) + assert(last != 0) + } } } -*/ From 7a50e85d4a0b2de2f162783ea7b3afd19565d2c4 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Wed, 9 Dec 2020 11:52:03 +0200 Subject: [PATCH 009/161] SelectMacro in progress --- shared/src/main/scala/gopher/Select.scala | 70 +++++++++++++++++------ 1 file changed, 52 insertions(+), 18 deletions(-) diff --git a/shared/src/main/scala/gopher/Select.scala b/shared/src/main/scala/gopher/Select.scala index 8aa79d0e..daa9aecc 100644 --- a/shared/src/main/scala/gopher/Select.scala +++ b/shared/src/main/scala/gopher/Select.scala @@ -12,9 +12,9 @@ class Select[F[_]:CpsSchedulingMonad](api: Gopher[F]): object Select: sealed trait SelectorCaseExpr - case class ReadExpression[F[_],A, S](ch: Expr[ReadChannel[F,A]], f: Expr[(FlowTermination, A) => F[S]]) - case class WriteExpression[F[_],A, S](ch: Expr[WriteChannel[F,A]], a: Expr[A], f: Expr[FlowTermination => F[S]]) - case class DefaultExpression[F[_],S](ch: Expr[FlowTermination => F[S]]) + case class ReadExpression[F[_], A, S](ch: Expr[ReadChannel[F,A]], f: Expr[A => S]) extends SelectorCaseExpr + case class WriteExpression[F[_], A, S](ch: Expr[WriteChannel[F,A]], a: Expr[A], f: Expr[() => F[S]]) extends SelectorCaseExpr + case class DefaultExpression[S](ch: Expr[ () => S ]) extends SelectorCaseExpr def onceImpl[F[_]:Type, A:Type](pf: Expr[PartialFunction[Any,A]])(using Quotes): Expr[A] = @@ -29,35 +29,69 @@ object Select: case Inlined(_,List(),body) => onceImplTree[F,A](body) case Match(scrutinee,cases) => - val caseExprs = cases map(x => parseCaseDef(x)) + val caseExprs = cases map(x => parseCaseDef[F,A](x)) ??? - def parseCaseDef(using Quotes)(caseDef: quotes.reflect.CaseDef): SelectorCaseExpr = + def parseCaseDef[F[_]:Type,S:Type](using Quotes)(caseDef: quotes.reflect.CaseDef): SelectorCaseExpr = import quotes.reflect._ caseDef.pattern match case Inlined(_,List(),body) => parseCaseDef(CaseDef(body, caseDef.guard, caseDef.rhs)) - case Typed(expr, TypeSelect(ch,"read")) => + case Typed(expr, tp@TypeSelect(ch,"read")) => if (caseDef.guard.isDefined) then report.error("guard in read should be empty", caseDef.asExpr) println(s"caseDef.rhs.tpe=${caseDef.rhs.tpe}" ) - case Bind(v, Typed(expr, TypeSelect(ch,"read"))) => - val paramName = v match - case Ident(x) => x - case _ => - report.error("expected identifier in read pattern", v.asExpr) - "x" - val mt = MethodType(List(paramName))(_ => List(v.tpe), _ => caseDef.rhs.tpe) + case b@Bind(v, tp@Typed(expr, TypeSelect(ch,"read"))) => + val mt = MethodType(List(v))(_ => List(tp.tpe), _ => caseDef.rhs.tpe) val readFun = Lambda(Symbol.spliceOwner,mt, - (owner, args) => substIdent(body,v,args.head).changeOwner(owner)) - ReadExpression(ch.asExpr,readFun) - case Bind(v, Typed(expr, TypeSelect(ch,"write"))) => - println(s"!!! write discovered" ) + (owner, args) => substIdent(caseDef.rhs,b.symbol,args.head.asInstanceOf[Term], owner).changeOwner(owner)) + if (ch.tpe <:< TypeRepr.of[ReadChannel[F,?]]) + tp.tpe.asType match + case '[a] => ReadExpression(ch.asExprOf[ReadChannel[F,a]],readFun.asExprOf[a=>S]) + case _ => + report.error("can't determinate read type", caseDef.pattern.asExpr) + throw new RuntimeException("Can't determinae read type") + else + report.error("read pattern is not a read channel", ch.asExpr) + throw new RuntimeException("Incorrect select caseDef") + case Bind(v, tp@Typed(expr, TypeSelect(ch,"write"))) => + val mt = MethodType(List())(_ => List(), _ => caseDef.rhs.tpe) + val writeFun = Lambda(Symbol.spliceOwner,mt, (owner,args) => caseDef.rhs.changeOwner(owner)) + val e = caseDef.guard match + case Some(condition) => + condition match + case Apply(quotes.reflect.Select(Ident(v1),method),List(expr)) => + if (v1 != v) { + report.error(s"write name mismatch ${v1}, expected ${v}", condition.asExpr) + throw new RuntimeException("macro failed") + } + expr + case _ => + report.error(s"Condition is not in form x==expr,${condition} ",condition.asExpr) + throw new RuntimeException("condition is not in writing form") + if (ch.tpe <:< TypeRepr.of[WriteChannel[F,?]]) then + tp.tpe.asType match + case '[a] => + WriteExpression(ch.asExprOf[WriteChannel[F,a]],e.asExprOf[a], writeFun.asExprOf[Unit=>S]) + case _ => + report.error("Can't determinate ") case _ => println(s"unparsed caseDef pattern: ${caseDef.pattern}" ) ??? - def substIdent(using Quotes)(term: Term, fromTerm: Term, toTerm: Term):Term = ??? + def substIdent(using Quotes)(term: quotes.reflect.Term, + fromSym: quotes.reflect.Symbol, + toTerm: quotes.reflect.Term, + owner: quotes.reflect.Symbol): quotes.reflect.Term = + import quotes.reflect._ + val argTransformer = new TreeMap() { + override def transformTerm(tree: Term)(owner: Symbol):Term = + tree match + case Ident(name) if tree.symbol == fromSym => toTerm + case _ => super.transformTerm(tree)(owner) + } + argTransformer.transformTerm(term)(owner) + From 24c9bf1eb9bbe7365e6e59b865d7d9a97e325086 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Thu, 10 Dec 2020 01:13:45 +0200 Subject: [PATCH 010/161] implemented simple Select, portet fibbonacy test --- shared/src/main/scala/gopher/Select.scala | 73 +++++++++++++------ .../channels/FibonnachySimpleTests.scala | 18 +++-- 2 files changed, 62 insertions(+), 29 deletions(-) diff --git a/shared/src/main/scala/gopher/Select.scala b/shared/src/main/scala/gopher/Select.scala index daa9aecc..daa25a36 100644 --- a/shared/src/main/scala/gopher/Select.scala +++ b/shared/src/main/scala/gopher/Select.scala @@ -3,44 +3,71 @@ package gopher import cps._ import scala.quoted._ +import scala.compiletime._ class Select[F[_]:CpsSchedulingMonad](api: Gopher[F]): - inline def apply[A](inline pf: PartialFunction[Any,A]): A = - ${ Select.onceImpl[F,A]('pf) } + inline def apply[A](inline pf: PartialFunction[Any,A]): A = + ${ + Select.onceImpl[F,A]('pf, '{summonInline[CpsSchedulingMonad[F]]} ) + } + + object Select: - sealed trait SelectorCaseExpr - case class ReadExpression[F[_], A, S](ch: Expr[ReadChannel[F,A]], f: Expr[A => S]) extends SelectorCaseExpr - case class WriteExpression[F[_], A, S](ch: Expr[WriteChannel[F,A]], a: Expr[A], f: Expr[() => F[S]]) extends SelectorCaseExpr - case class DefaultExpression[S](ch: Expr[ () => S ]) extends SelectorCaseExpr + sealed trait SelectGroupExpr[F[_],S]: + def toExpr: Expr[SelectGroup[F,S]] + + sealed trait SelectorCaseExpr[F[_]:Type, S:Type]: + type Monad[X] = F[X] + def appended(base: Expr[SelectGroup[F,S]])(using Quotes): Expr[SelectGroup[F,S]] + + case class ReadExpression[F[_]:Type, A:Type, S:Type](ch: Expr[ReadChannel[F,A]], f: Expr[A => S]) extends SelectorCaseExpr[F,S]: + def appended(base: Expr[SelectGroup[F,S]])(using Quotes): Expr[SelectGroup[F,S]] = + '{ $base.reading($ch)($f) } + + case class WriteExpression[F[_]:Type, A:Type, S:Type](ch: Expr[WriteChannel[F,A]], a: Expr[A], f: Expr[() => S]) extends SelectorCaseExpr[F,S]: + def appended(base: Expr[SelectGroup[F,S]])(using Quotes): Expr[SelectGroup[F,S]] = + '{ $base.writing($ch,$a)($f()) } + + case class DefaultExpression[F[_]:Type,S:Type](ch: Expr[ () => S ]) extends SelectorCaseExpr[F,S]: + def appended(base: Expr[SelectGroup[F,S]])(using Quotes): Expr[SelectGroup[F,S]] = + '{ ??? } + - def onceImpl[F[_]:Type, A:Type](pf: Expr[PartialFunction[Any,A]])(using Quotes): Expr[A] = + def onceImpl[F[_]:Type, A:Type](pf: Expr[PartialFunction[Any,A]], m: Expr[CpsSchedulingMonad[F]])(using Quotes): Expr[A] = import quotes.reflect._ - onceImplTree[F,A](Term.of(pf)).asExprOf[A] + onceImplTree[F,A](Term.of(pf), m).asExprOf[A] - def onceImplTree[F[_]:Type, A:Type](using Quotes)(pf: quotes.reflect.Term): quotes.reflect.Term = + def onceImplTree[F[_]:Type, S:Type](using Quotes)(pf: quotes.reflect.Term, m: Expr[CpsSchedulingMonad[F]]): quotes.reflect.Term = import quotes.reflect._ pf match case Lambda(valDefs, body) => - onceImplTree[F,A](body) + onceImplTree[F,S](body, m) case Inlined(_,List(),body) => - onceImplTree[F,A](body) + onceImplTree[F,S](body, m) case Match(scrutinee,cases) => - val caseExprs = cases map(x => parseCaseDef[F,A](x)) - ??? + //val caseExprs = cases map(x => parseCaseDef[F,A](x)) + //if (caseExprs.find(_.isInstanceOf[DefaultExpression[?]]).isDefined) { + // report.error("default is not supported") + //} + val s0 = '{ + new SelectGroup[F,S](using $m) + } + val g: Expr[SelectGroup[F,S]] = cases.foldLeft(s0){(s,e) => + parseCaseDef(e).appended(s) + } + val r = '{ $g.run } + Term.of(r) + - def parseCaseDef[F[_]:Type,S:Type](using Quotes)(caseDef: quotes.reflect.CaseDef): SelectorCaseExpr = + def parseCaseDef[F[_]:Type,S:Type](using Quotes)(caseDef: quotes.reflect.CaseDef): SelectorCaseExpr[F,S] = import quotes.reflect._ caseDef.pattern match case Inlined(_,List(),body) => parseCaseDef(CaseDef(body, caseDef.guard, caseDef.rhs)) - case Typed(expr, tp@TypeSelect(ch,"read")) => - if (caseDef.guard.isDefined) then - report.error("guard in read should be empty", caseDef.asExpr) - println(s"caseDef.rhs.tpe=${caseDef.rhs.tpe}" ) case b@Bind(v, tp@Typed(expr, TypeSelect(ch,"read"))) => val mt = MethodType(List(v))(_ => List(tp.tpe), _ => caseDef.rhs.tpe) val readFun = Lambda(Symbol.spliceOwner,mt, @@ -72,12 +99,16 @@ object Select: if (ch.tpe <:< TypeRepr.of[WriteChannel[F,?]]) then tp.tpe.asType match case '[a] => - WriteExpression(ch.asExprOf[WriteChannel[F,a]],e.asExprOf[a], writeFun.asExprOf[Unit=>S]) + WriteExpression(ch.asExprOf[WriteChannel[F,a]],e.asExprOf[a], writeFun.asExprOf[()=>S]) case _ => - report.error("Can't determinate ") + report.error("Can't determinate type of write", tp.asExpr) + throw new RuntimeException("Macro error") + else + report.error("Write channel expected", ch.asExpr) + throw new RuntimeException("not a write channel") case _ => println(s"unparsed caseDef pattern: ${caseDef.pattern}" ) - ??? + ??? def substIdent(using Quotes)(term: quotes.reflect.Term, diff --git a/shared/src/test/scala/gopher/channels/FibonnachySimpleTests.scala b/shared/src/test/scala/gopher/channels/FibonnachySimpleTests.scala index 43da2d3f..22b57a7f 100644 --- a/shared/src/test/scala/gopher/channels/FibonnachySimpleTests.scala +++ b/shared/src/test/scala/gopher/channels/FibonnachySimpleTests.scala @@ -21,8 +21,9 @@ class FibbonachySimpleTest extends FunSuite { while(!done) { // TODO: add select group to given SelectGroup[Future,Unit]().writing(c, x){ + val x0 = x x=y - y=x+y + y=x0+y } .reading(quit){ v => done = true @@ -37,9 +38,10 @@ class FibbonachySimpleTest extends FunSuite { var done = false while(!done) { select{ - case z: c.write if (z == x) => - x = y - y = z + y + case out: c.write if (out == x) => + val tmp = y + y = x + y + x = tmp case q: quit.read => done = true } @@ -50,10 +52,10 @@ class FibbonachySimpleTest extends FunSuite { acceptor: Long => Unit, n:Int): Future[Unit] = { val fib = makeChannel[Long]() val q = makeChannel[Int]() - val start = fibonaccy0(fib, q) + val start = starter(fib,q) async{ for( i <- 1 to n) { - var x = fib.read + val x = fib.read acceptor(x) } q <~ 1 @@ -63,7 +65,7 @@ class FibbonachySimpleTest extends FunSuite { test("simple fibonnachy fun (no macroses)") { @volatile var last: Long = 0L async{ - await(run(fibonaccy0, last = _, 50)) + await(run(fibonaccy0, last = _, 40)) assert(last != 0) } } @@ -71,7 +73,7 @@ class FibbonachySimpleTest extends FunSuite { test("fibonnachy fun (select macros)") { @volatile var last: Long = 0L async{ - await(run(fibonaccy0, last = _, 50)) + await(run(fibonaccy0, last = _, 40)) assert(last != 0) } } From 3e6ab7da6e441a0e9ad435fcead67ec6f8975133 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Thu, 10 Dec 2020 09:09:41 +0200 Subject: [PATCH 011/161] allow using of value, just written to channel, inside caseDef rhs --- shared/src/main/scala/gopher/Select.scala | 20 +++++++++++++------ .../src/main/scala/gopher/SelectGroup.scala | 8 ++++---- .../channels/FibonnachySimpleTests.scala | 12 +++++------ 3 files changed, 23 insertions(+), 17 deletions(-) diff --git a/shared/src/main/scala/gopher/Select.scala b/shared/src/main/scala/gopher/Select.scala index daa25a36..d9041b14 100644 --- a/shared/src/main/scala/gopher/Select.scala +++ b/shared/src/main/scala/gopher/Select.scala @@ -27,9 +27,9 @@ object Select: def appended(base: Expr[SelectGroup[F,S]])(using Quotes): Expr[SelectGroup[F,S]] = '{ $base.reading($ch)($f) } - case class WriteExpression[F[_]:Type, A:Type, S:Type](ch: Expr[WriteChannel[F,A]], a: Expr[A], f: Expr[() => S]) extends SelectorCaseExpr[F,S]: + case class WriteExpression[F[_]:Type, A:Type, S:Type](ch: Expr[WriteChannel[F,A]], a: Expr[A], f: Expr[A => S]) extends SelectorCaseExpr[F,S]: def appended(base: Expr[SelectGroup[F,S]])(using Quotes): Expr[SelectGroup[F,S]] = - '{ $base.writing($ch,$a)($f()) } + '{ $base.writing($ch,$a)($f) } case class DefaultExpression[F[_]:Type,S:Type](ch: Expr[ () => S ]) extends SelectorCaseExpr[F,S]: def appended(base: Expr[SelectGroup[F,S]])(using Quotes): Expr[SelectGroup[F,S]] = @@ -81,9 +81,12 @@ object Select: else report.error("read pattern is not a read channel", ch.asExpr) throw new RuntimeException("Incorrect select caseDef") - case Bind(v, tp@Typed(expr, TypeSelect(ch,"write"))) => - val mt = MethodType(List())(_ => List(), _ => caseDef.rhs.tpe) - val writeFun = Lambda(Symbol.spliceOwner,mt, (owner,args) => caseDef.rhs.changeOwner(owner)) + case b@Bind(v, tp@Typed(expr, TypeSelect(ch,"write"))) => + val mt = MethodType(List(v))(_ => List(tp.tpe), _ => caseDef.rhs.tpe) + //val newSym = Symbol.newVal(Symbol.spliceOwner,v,tp.tpe.widen,Flags.EmptyFlags, Symbol.noSymbol) + //val newIdent = Ref(newSym) + val writeFun = Lambda(Symbol.spliceOwner,mt, (owner,args) => + substIdent(caseDef.rhs,b.symbol, args.head.asInstanceOf[Term], owner).changeOwner(owner)) val e = caseDef.guard match case Some(condition) => condition match @@ -96,10 +99,15 @@ object Select: case _ => report.error(s"Condition is not in form x==expr,${condition} ",condition.asExpr) throw new RuntimeException("condition is not in writing form") + case None => + // are we have shadowed symbol with the same name? + // mb try to find one ? + report.error("condition in write is required") + throw new RuntimeException("condition in wrte is required") if (ch.tpe <:< TypeRepr.of[WriteChannel[F,?]]) then tp.tpe.asType match case '[a] => - WriteExpression(ch.asExprOf[WriteChannel[F,a]],e.asExprOf[a], writeFun.asExprOf[()=>S]) + WriteExpression(ch.asExprOf[WriteChannel[F,a]],e.asExprOf[a], writeFun.asExprOf[a=>S]) case _ => report.error("Can't determinate type of write", tp.asExpr) throw new RuntimeException("Macro error") diff --git a/shared/src/main/scala/gopher/SelectGroup.scala b/shared/src/main/scala/gopher/SelectGroup.scala index 7652cd5a..189cda44 100644 --- a/shared/src/main/scala/gopher/SelectGroup.scala +++ b/shared/src/main/scala/gopher/SelectGroup.scala @@ -56,16 +56,16 @@ class SelectGroup[F[_]:CpsSchedulingMonad, S]: }) m.pure(this) - def writing[A](ch: WriteChannel[F,A], a:A)(f: =>S ): SelectGroup[F,S] = + def writing[A](ch: WriteChannel[F,A], a:A)(f: A =>S ): SelectGroup[F,S] = addWriter[A](ch,a,{ - case Success(()) => m.tryPure(f) + case Success(()) => m.tryPure(f(a)) case Failure(ex) => m.error(ex) }) this - def writing_async[A](ch: WriteChannel[F,A], a:A) (f: ()=> F[S] ): F[this.type] = + def writing_async[A](ch: WriteChannel[F,A], a:A) (f: A => F[S] ): F[this.type] = addWriter[A](ch,a,{ - case Success(()) => m.tryImpure(f()) + case Success(()) => m.tryImpure(f(a)) case Failure(ex) => m.error(ex) }) m.pure(this) diff --git a/shared/src/test/scala/gopher/channels/FibonnachySimpleTests.scala b/shared/src/test/scala/gopher/channels/FibonnachySimpleTests.scala index 22b57a7f..150b80ca 100644 --- a/shared/src/test/scala/gopher/channels/FibonnachySimpleTests.scala +++ b/shared/src/test/scala/gopher/channels/FibonnachySimpleTests.scala @@ -16,12 +16,11 @@ class FibbonachySimpleTest extends FunSuite { def fibonaccy0(c: WriteChannel[Future,Long], quit: ReadChannel[Future,Int]): Future[Unit] = async[Future]{ - var (x,y) = (0,1) + var (x,y) = (0L,1L) var done = false while(!done) { // TODO: add select group to given - SelectGroup[Future,Unit]().writing(c, x){ - val x0 = x + SelectGroup[Future,Unit]().writing(c, x){ x0 => x=y y=x0+y } @@ -38,10 +37,9 @@ class FibbonachySimpleTest extends FunSuite { var done = false while(!done) { select{ - case out: c.write if (out == x) => - val tmp = y - y = x + y - x = tmp + case z: c.write if (z == x) => + x = z + y = z + y case q: quit.read => done = true } From 8840965c7fc5e92b45f6271ea6f04184916e4bd6 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Fri, 11 Dec 2020 08:27:37 +0200 Subject: [PATCH 012/161] added Time --- js/src/main/scala/gopher/JSGopher.scala | 6 + jvm/src/main/scala/gopher/JVMGopher.scala | 9 ++ shared/src/main/scala/gopher/Gopher.scala | 10 +- shared/src/main/scala/gopher/GopherAPI.scala | 4 +- .../src/main/scala/gopher/ReadChannel.scala | 5 +- shared/src/main/scala/gopher/Select.scala | 17 +-- .../src/main/scala/gopher/SelectGroup.scala | 55 +++++++-- shared/src/main/scala/gopher/Time.scala | 110 ++++++++++++++++++ .../src/main/scala/gopher/WriteChannel.scala | 10 +- .../channels/FibonnachySimpleTests.scala | 3 +- 10 files changed, 204 insertions(+), 25 deletions(-) create mode 100644 shared/src/main/scala/gopher/Time.scala diff --git a/js/src/main/scala/gopher/JSGopher.scala b/js/src/main/scala/gopher/JSGopher.scala index 1bb42f10..afc6e392 100644 --- a/js/src/main/scala/gopher/JSGopher.scala +++ b/js/src/main/scala/gopher/JSGopher.scala @@ -1,6 +1,7 @@ package gopher import cps._ +import java.util.Timer class JSGopher[F[_]:CpsSchedulingMonad](cfg: JSGopherConfig) extends Gopher[F]: @@ -11,6 +12,9 @@ class JSGopher[F[_]:CpsSchedulingMonad](cfg: JSGopherConfig) extends Gopher[F]: else impl.BufferedChannel[F,A](this,bufSize) + def timer = JSGopher.timer + + object JSGopher extends GopherAPI: def apply[F[_]:CpsSchedulingMonad](cfg: GopherConfig):Gopher[F] = @@ -19,3 +23,5 @@ object JSGopher extends GopherAPI: case jcfg:JSGopherConfig => jcfg new JSGopher[F](jsConfig) + val timer = new Timer("gopher") + diff --git a/jvm/src/main/scala/gopher/JVMGopher.scala b/jvm/src/main/scala/gopher/JVMGopher.scala index 48d6ad1f..75748dc6 100644 --- a/jvm/src/main/scala/gopher/JVMGopher.scala +++ b/jvm/src/main/scala/gopher/JVMGopher.scala @@ -6,6 +6,7 @@ import gopher.impl._ import java.util.concurrent.Executors import java.util.concurrent.ExecutorService import java.util.concurrent.ForkJoinPool +import java.util.Timer @@ -18,6 +19,12 @@ class JVMGopher[F[_]:CpsSchedulingMonad](cfg: JVMGopherConfig) extends Gopher[F] else GuardedSPSCBufferedChannel[F,A](this, bufSize, cfg.controlExecutor,cfg.taskExecutor) + def timer = JVMGopher.timer + + def taskExecutor = cfg.taskExecutor + + + object JVMGopher extends GopherAPI: def apply[F[_]:CpsSchedulingMonad](cfg: GopherConfig):Gopher[F] = @@ -26,6 +33,8 @@ object JVMGopher extends GopherAPI: case jcfg:JVMGopherConfig => jcfg new JVMGopher[F](jvmConfig) + lazy val timer = new Timer("gopher") + lazy val defaultConfig=JVMGopherConfig( controlExecutor=Executors.newFixedThreadPool(2), taskExecutor=ForkJoinPool.commonPool() diff --git a/shared/src/main/scala/gopher/Gopher.scala b/shared/src/main/scala/gopher/Gopher.scala index 690defda..80ae289b 100644 --- a/shared/src/main/scala/gopher/Gopher.scala +++ b/shared/src/main/scala/gopher/Gopher.scala @@ -1,15 +1,23 @@ package gopher import cps._ +import java.util.Timer trait Gopher[F[_]:CpsSchedulingMonad]: type Monad[X] = F[X] + def asyncMonad: CpsSchedulingMonad[F] = summon[CpsSchedulingMonad[F]] def makeChannel[A](bufSize:Int = 0): Channel[F,A,A] def select: Select[F] = - new Select[F](this) + new Select[F](this) + + def time: Time[F] = new Time[F](this) + + + def timer: Timer + def makeChannel[A](bufSize:Int = 1)(using g:Gopher[?]):Channel[g.Monad,A,A] = diff --git a/shared/src/main/scala/gopher/GopherAPI.scala b/shared/src/main/scala/gopher/GopherAPI.scala index 6fe67caf..7d76aab2 100644 --- a/shared/src/main/scala/gopher/GopherAPI.scala +++ b/shared/src/main/scala/gopher/GopherAPI.scala @@ -12,9 +12,6 @@ trait GopherAPI: def apply[F[_]:CpsSchedulingMonad](cfg:GopherConfig = DefaultGopherConfig): Gopher[F] - - - /** * Shared gopehr api, which is initialized by platofrm part, * Primary used for cross-platforming test, you shoul initialize one of platform API @@ -28,6 +25,7 @@ object SharedGopherAPI { api.apply[F](cfg) + def api: GopherAPI = if (_api.isEmpty) then initPlatformSpecific() diff --git a/shared/src/main/scala/gopher/ReadChannel.scala b/shared/src/main/scala/gopher/ReadChannel.scala index a9ac2831..0e9a4514 100644 --- a/shared/src/main/scala/gopher/ReadChannel.scala +++ b/shared/src/main/scala/gopher/ReadChannel.scala @@ -25,8 +25,9 @@ trait ReadChannel[F[_], A]: inline def ? : A = await(aread)(using rAsyncMonad) - object Read: - def unapply(): Option[A] = ??? + //object Read: + // inline def unapply(): Option[A] = + // Some(read) def aOptRead: F[Option[A]] = asyncMonad.adoptCallbackStyle( f => diff --git a/shared/src/main/scala/gopher/Select.scala b/shared/src/main/scala/gopher/Select.scala index d9041b14..c52d96d9 100644 --- a/shared/src/main/scala/gopher/Select.scala +++ b/shared/src/main/scala/gopher/Select.scala @@ -9,9 +9,10 @@ class Select[F[_]:CpsSchedulingMonad](api: Gopher[F]): inline def apply[A](inline pf: PartialFunction[Any,A]): A = ${ - Select.onceImpl[F,A]('pf, '{summonInline[CpsSchedulingMonad[F]]} ) + Select.onceImpl[F,A]('pf, '{summonInline[CpsSchedulingMonad[F]]}, 'api ) } - + + def group[S](): SelectGroup[F,S] = new SelectGroup[F,S](api) object Select: @@ -37,24 +38,24 @@ object Select: - def onceImpl[F[_]:Type, A:Type](pf: Expr[PartialFunction[Any,A]], m: Expr[CpsSchedulingMonad[F]])(using Quotes): Expr[A] = + def onceImpl[F[_]:Type, A:Type](pf: Expr[PartialFunction[Any,A]], m: Expr[CpsSchedulingMonad[F]], api: Expr[Gopher[F]])(using Quotes): Expr[A] = import quotes.reflect._ - onceImplTree[F,A](Term.of(pf), m).asExprOf[A] + onceImplTree[F,A](Term.of(pf), m, api).asExprOf[A] - def onceImplTree[F[_]:Type, S:Type](using Quotes)(pf: quotes.reflect.Term, m: Expr[CpsSchedulingMonad[F]]): quotes.reflect.Term = + def onceImplTree[F[_]:Type, S:Type](using Quotes)(pf: quotes.reflect.Term, m: Expr[CpsSchedulingMonad[F]], api: Expr[Gopher[F]]): quotes.reflect.Term = import quotes.reflect._ pf match case Lambda(valDefs, body) => - onceImplTree[F,S](body, m) + onceImplTree[F,S](body, m, api) case Inlined(_,List(),body) => - onceImplTree[F,S](body, m) + onceImplTree[F,S](body, m, api) case Match(scrutinee,cases) => //val caseExprs = cases map(x => parseCaseDef[F,A](x)) //if (caseExprs.find(_.isInstanceOf[DefaultExpression[?]]).isDefined) { // report.error("default is not supported") //} val s0 = '{ - new SelectGroup[F,S](using $m) + new SelectGroup[F,S]($api)(using $m) } val g: Expr[SelectGroup[F,S]] = cases.foldLeft(s0){(s,e) => parseCaseDef(e).appended(s) diff --git a/shared/src/main/scala/gopher/SelectGroup.scala b/shared/src/main/scala/gopher/SelectGroup.scala index 189cda44..429bd2a9 100644 --- a/shared/src/main/scala/gopher/SelectGroup.scala +++ b/shared/src/main/scala/gopher/SelectGroup.scala @@ -3,15 +3,19 @@ package gopher import cps._ import gopher.impl._ import java.util.concurrent.atomic.AtomicInteger +import java.util.concurrent.atomic.AtomicLong +import java.util.TimerTask; import scala.annotation.unchecked.uncheckedVariance import scala.util._ +import scala.concurrent.duration._ +import scala.language.postfixOps /** * Select group is a virtual 'lock' object, where only * ne fro rieader and writer can exists at the sae time. **/ -class SelectGroup[F[_]:CpsSchedulingMonad, S]: +class SelectGroup[F[_]:CpsSchedulingMonad, S](api: Gopher[F]): /** * instance of select group created for call of select. @@ -20,6 +24,8 @@ class SelectGroup[F[_]:CpsSchedulingMonad, S]: var call: Try[S] => Unit = { _ => () } private inline def m = summon[CpsSchedulingMonad[F]] val retval = m.adoptCallbackStyle[S](f => call=f) + val startTime = new AtomicLong(0L) + var timeoutTask: Option[TimerTask] = None def addReader[A](ch: ReadChannel[F,A], action: Try[A]=>F[S]): Unit = val record = ReaderRecord(ch, action) @@ -29,6 +35,22 @@ class SelectGroup[F[_]:CpsSchedulingMonad, S]: val record = WriterRecord(ch, element, action) ch.addWriter(record) + def setTimeout(timeout: FiniteDuration, action: Try[FiniteDuration] => F[S]): Unit = + timeoutTask.foreach(_.cancel()) + val newTask = new TimerTask() { + val record = new TimeoutRecord(timeout,action) + override def run(): Unit = { + val v = System.currentTimeMillis() - startTime.get() + record.capture() match + case Some(f) => f(Success(v milliseconds)) + case None => // do nothing. + } + } + timeoutTask = Some(newTask) + api.timer.schedule(newTask, System.currentTimeMillis() + timeout.toMillis); + + + def step():F[S] = retval @@ -87,6 +109,7 @@ class SelectGroup[F[_]:CpsSchedulingMonad, S]: override def capture(): Option[Try[A]=>Unit] = if waitState.compareAndSet(0,1) then Some(v => { + timeoutTask.foreach(_.cancel()) m.spawn( m.mapTry(action(v))(x => call(x)) ) @@ -104,13 +127,27 @@ class SelectGroup[F[_]:CpsSchedulingMonad, S]: type State = S override def capture(): Option[(A,Try[Unit]=>Unit)] = - if waitState.compareAndSet(0,1) then - Some((element, (v:Try[Unit]) => m.spawn( - m.mapTry(action(v))(x=>call(x)) - ))) - else - None - - + if waitState.compareAndSet(0,1) then + Some((element, (v:Try[Unit]) => { + timeoutTask.foreach(_.cancel()) + m.spawn( + m.mapTry(action(v))(x=>call(x)) + )} + )) + else + None + + + case class TimeoutRecord(duration: FiniteDuration, + action: Try[FiniteDuration] => F[S], + ) extends Expiration: + + def capture(): Option[Try[FiniteDuration] => Unit] = + if (waitState.compareAndSet(0,1)) then + Some((v:Try[FiniteDuration]) => + m.spawn(m.mapTry(action(v))(x => call(x))) + ) + else + None diff --git a/shared/src/main/scala/gopher/Time.scala b/shared/src/main/scala/gopher/Time.scala new file mode 100644 index 00000000..cfc34b4e --- /dev/null +++ b/shared/src/main/scala/gopher/Time.scala @@ -0,0 +1,110 @@ +package gopher + +import cps._ + + +import scala.concurrent._ +import scala.concurrent.duration._ +import java.util.concurrent.TimeUnit + +import scala.language.experimental.macros +import scala.util.Failure + +/** + * Time API, simular to one in golang standard library. + * @see gopherApi#time + */ +class Time[F[_]](gopherAPI: Gopher[F]) { + + + def after(duration: FiniteDuration): ReadChannel[F,FiniteDuration] = + { + ??? + /* + val ch = OneTimeChannel.apply[Instant]()(gopherAPI) + gopherAPI.actorSystem.scheduler.scheduleOnce(duration){ + ch.awrite(Instant.now()) + }(ec) + ch + */ + } + + def asleep(duration: FiniteDuration): F[FiniteDuration] = + { + ??? + /* + val p = Promise[Instant]() + gopherAPI.actorSystem.scheduler.scheduleOnce(duration){ + p success Instant.now() + }(ec) + p.future + */ + } + + inline def sleep(duration: FiniteDuration): FiniteDuration = + given CpsSchedulingMonad[F] = gopherAPI.asyncMonad + await(asleep(duration)) + + /** + * create ticker. When somebody read this ticker, than one receive duration + * messages. When nobody reading - messages are expired. + * @param duration + * @return + */ + def tick(duration: FiniteDuration): ReadChannel[F,FiniteDuration] = + { + ??? + //newTicker(duration) + } + + /* + class Ticker(duration: FiniteDuration) + { + + val ch = ExpireChannel[Instant](duration,0)(gopherAPI) + val cancellable = gopherAPI.actorSystem.scheduler.schedule(duration,duration)(tick)(ec) + gopherAPI.actorSystem.registerOnTermination{ + if (!cancellable.isCancelled) cancellable.cancel() + } + + def tick():Unit = { + if (!cancellable.isCancelled) { + ch.awrite(Instant.now()).onComplete{ + case Failure(ex:ChannelClosedException) => cancellable.cancel() + case Failure(ex) => cancellable.cancel() // strange, but stop. + case _ => + }(ec) + } + } + + } + + def newTicker(duration: FiniteDuration): Channel[Instant] = + { + (new Ticker(duration)).ch + } + */ + + def now(): FiniteDuration = + FiniteDuration(System.currentTimeMillis(),TimeUnit.MILLISECONDS) + +} + +object Time: + + /** + * Used in selector shugar for specyfying tineout. + *``` + * select{ + * ...... + * case t: Time.after if t > expr => doSomething + * } + *``` + * is a sugar for to selectGroup.{..}.setTimeout(expr, t=>doSomething) + *@see Select + **/ + type after = FiniteDuration + + + def after[F[_]](duration: FiniteDuration)(using Gopher[F]): ReadChannel[F,FiniteDuration] = + summon[Gopher[F]].time.after(duration) diff --git a/shared/src/main/scala/gopher/WriteChannel.scala b/shared/src/main/scala/gopher/WriteChannel.scala index 32390933..320c41c7 100644 --- a/shared/src/main/scala/gopher/WriteChannel.scala +++ b/shared/src/main/scala/gopher/WriteChannel.scala @@ -17,11 +17,19 @@ trait WriteChannel[F[_], A]: addWriter(SimpleWriter(a, f)) ) - inline def write(a:A): Unit = await(awrite(a))(using asyncMonad) + object write: + inline def apply(a:A): Unit = await(awrite(a))(using asyncMonad) + inline def unapply(a:A): Some[A] = ??? @targetName("write1") inline def <~ (a:A): Unit = await(awrite(a))(using asyncMonad) + //def Write(x:A):WritePattern = new WritePattern(x) + + class WritePattern(x:A): + inline def unapply(y:Any): Option[A] = + Some(x) + def addWriter(writer: Writer[A]): Unit def awriteAll(collection: IterableOnce[A]): F[Unit] = diff --git a/shared/src/test/scala/gopher/channels/FibonnachySimpleTests.scala b/shared/src/test/scala/gopher/channels/FibonnachySimpleTests.scala index 150b80ca..6efb9d66 100644 --- a/shared/src/test/scala/gopher/channels/FibonnachySimpleTests.scala +++ b/shared/src/test/scala/gopher/channels/FibonnachySimpleTests.scala @@ -20,7 +20,7 @@ class FibbonachySimpleTest extends FunSuite { var done = false while(!done) { // TODO: add select group to given - SelectGroup[Future,Unit]().writing(c, x){ x0 => + select.group[Unit]().writing(c, x){ x0 => x=y y=x0+y } @@ -46,6 +46,7 @@ class FibbonachySimpleTest extends FunSuite { } } + def run(starter: (WriteChannel[Future,Long], ReadChannel[Future,Int])=> Future[Unit], acceptor: Long => Unit, n:Int): Future[Unit] = { val fib = makeChannel[Long]() From 07bdd5fc37dc611a319b3d992d25d86cdb3c6dce Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Fri, 11 Dec 2020 20:16:44 +0200 Subject: [PATCH 013/161] aded timeout --- shared/src/main/scala/gopher/Select.scala | 103 +++++++++++------- .../src/main/scala/gopher/SelectGroup.scala | 34 +++++- .../channels/FibonnachySimpleTests.scala | 4 +- 3 files changed, 94 insertions(+), 47 deletions(-) diff --git a/shared/src/main/scala/gopher/Select.scala b/shared/src/main/scala/gopher/Select.scala index c52d96d9..bc69a31e 100644 --- a/shared/src/main/scala/gopher/Select.scala +++ b/shared/src/main/scala/gopher/Select.scala @@ -4,6 +4,7 @@ import cps._ import scala.quoted._ import scala.compiletime._ +import scala.concurrent.duration._ class Select[F[_]:CpsSchedulingMonad](api: Gopher[F]): @@ -26,15 +27,15 @@ object Select: case class ReadExpression[F[_]:Type, A:Type, S:Type](ch: Expr[ReadChannel[F,A]], f: Expr[A => S]) extends SelectorCaseExpr[F,S]: def appended(base: Expr[SelectGroup[F,S]])(using Quotes): Expr[SelectGroup[F,S]] = - '{ $base.reading($ch)($f) } + '{ $base.onRead($ch)($f) } case class WriteExpression[F[_]:Type, A:Type, S:Type](ch: Expr[WriteChannel[F,A]], a: Expr[A], f: Expr[A => S]) extends SelectorCaseExpr[F,S]: def appended(base: Expr[SelectGroup[F,S]])(using Quotes): Expr[SelectGroup[F,S]] = - '{ $base.writing($ch,$a)($f) } + '{ $base.onWrite($ch,$a)($f) } - case class DefaultExpression[F[_]:Type,S:Type](ch: Expr[ () => S ]) extends SelectorCaseExpr[F,S]: + case class TimeoutExpression[F[_]:Type,S:Type](t: Expr[FiniteDuration], f: Expr[ FiniteDuration => S ]) extends SelectorCaseExpr[F,S]: def appended(base: Expr[SelectGroup[F,S]])(using Quotes): Expr[SelectGroup[F,S]] = - '{ ??? } + '{ $base.onTimeout($t)($f) } @@ -70,54 +71,71 @@ object Select: case Inlined(_,List(),body) => parseCaseDef(CaseDef(body, caseDef.guard, caseDef.rhs)) case b@Bind(v, tp@Typed(expr, TypeSelect(ch,"read"))) => - val mt = MethodType(List(v))(_ => List(tp.tpe), _ => caseDef.rhs.tpe) - val readFun = Lambda(Symbol.spliceOwner,mt, - (owner, args) => substIdent(caseDef.rhs,b.symbol,args.head.asInstanceOf[Term], owner).changeOwner(owner)) + val readFun = makeLambda(v,tp.tpe,b.symbol,caseDef.rhs) if (ch.tpe <:< TypeRepr.of[ReadChannel[F,?]]) tp.tpe.asType match case '[a] => ReadExpression(ch.asExprOf[ReadChannel[F,a]],readFun.asExprOf[a=>S]) case _ => - report.error("can't determinate read type", caseDef.pattern.asExpr) - throw new RuntimeException("Can't determinae read type") + reportError("can't determinate read type", caseDef.pattern.asExpr) else - report.error("read pattern is not a read channel", ch.asExpr) - throw new RuntimeException("Incorrect select caseDef") + reportError("read pattern is not a read channel", ch.asExpr) case b@Bind(v, tp@Typed(expr, TypeSelect(ch,"write"))) => - val mt = MethodType(List(v))(_ => List(tp.tpe), _ => caseDef.rhs.tpe) - //val newSym = Symbol.newVal(Symbol.spliceOwner,v,tp.tpe.widen,Flags.EmptyFlags, Symbol.noSymbol) - //val newIdent = Ref(newSym) - val writeFun = Lambda(Symbol.spliceOwner,mt, (owner,args) => - substIdent(caseDef.rhs,b.symbol, args.head.asInstanceOf[Term], owner).changeOwner(owner)) - val e = caseDef.guard match - case Some(condition) => - condition match - case Apply(quotes.reflect.Select(Ident(v1),method),List(expr)) => - if (v1 != v) { - report.error(s"write name mismatch ${v1}, expected ${v}", condition.asExpr) - throw new RuntimeException("macro failed") - } - expr - case _ => - report.error(s"Condition is not in form x==expr,${condition} ",condition.asExpr) - throw new RuntimeException("condition is not in writing form") - case None => - // are we have shadowed symbol with the same name? - // mb try to find one ? - report.error("condition in write is required") - throw new RuntimeException("condition in wrte is required") + val writeFun = makeLambda(v,tp.tpe, b.symbol, caseDef.rhs) + val e = matchCaseDefCondition(caseDef, v) if (ch.tpe <:< TypeRepr.of[WriteChannel[F,?]]) then tp.tpe.asType match case '[a] => WriteExpression(ch.asExprOf[WriteChannel[F,a]],e.asExprOf[a], writeFun.asExprOf[a=>S]) case _ => - report.error("Can't determinate type of write", tp.asExpr) - throw new RuntimeException("Macro error") + reportError("Can't determinate type of write", tp.asExpr) else - report.error("Write channel expected", ch.asExpr) - throw new RuntimeException("not a write channel") + reportError("Write channel expected", ch.asExpr) + case b@Bind(v, tp@Typed(expr, TypeSelect(ch,"after"))) => + val timeoutFun = makeLambda(v, tp.tpe, b.symbol, caseDef.rhs) + val e = matchCaseDefCondition(caseDef, v) + if (ch.tpe =:= TypeRepr.of[gopher.Time]) + TimeoutExpression(e.asExprOf[FiniteDuration], timeoutFun.asExprOf[FiniteDuration => S]) + else + reportError(s"Expected Time, we have ${ch.show}", ch.asExpr) + case _ => + report.error( + s""" + expected one of: + v: channel.read + v: channel.write if v == expr + v: Time.after if v == expr + we have + ${caseDef.pattern.show} + """, caseDef.pattern.asExpr) + reportError(s"unparsed caseDef pattern: ${caseDef.pattern}", caseDef.pattern.asExpr) + + end parseCaseDef + + def matchCaseDefCondition(using Quotes)(caseDef: quotes.reflect.CaseDef, v: String): quotes.reflect.Term = + import quotes.reflect._ + caseDef.guard match + case Some(condition) => + condition match + case Apply(quotes.reflect.Select(Ident(v1),method),List(expr)) => + if (v1 != v) { + reportError(s"write name mismatch ${v1}, expected ${v}", condition.asExpr) + } + // TODO: check that method is '=='' + expr + case _ => + reportError(s"Condition is not in form x==expr,${condition} ",condition.asExpr) case _ => - println(s"unparsed caseDef pattern: ${caseDef.pattern}" ) - ??? + reportError(s"Condition is required ",caseDef.pattern.asExpr) + + + def makeLambda(using Quotes)(argName: String, + argType: quotes.reflect.TypeRepr, + oldArgSymbol: quotes.reflect.Symbol, + body: quotes.reflect.Term): quotes.reflect.Term = + import quotes.reflect._ + val mt = MethodType(List(argName))(_ => List(argType), _ => body.tpe.widen) + Lambda(Symbol.spliceOwner, mt, (owner,args) => + substIdent(body,oldArgSymbol, args.head.asInstanceOf[Term], owner).changeOwner(owner)) def substIdent(using Quotes)(term: quotes.reflect.Term, @@ -133,5 +151,12 @@ object Select: } argTransformer.transformTerm(term)(owner) + def reportError(message: String, posExpr: Expr[?])(using Quotes): Nothing = + import quotes.reflect._ + report.error(message, posExpr) + throw new RuntimeException(s"Error in macro: $message") + + + diff --git a/shared/src/main/scala/gopher/SelectGroup.scala b/shared/src/main/scala/gopher/SelectGroup.scala index 429bd2a9..17e7cd3c 100644 --- a/shared/src/main/scala/gopher/SelectGroup.scala +++ b/shared/src/main/scala/gopher/SelectGroup.scala @@ -59,11 +59,11 @@ class SelectGroup[F[_]:CpsSchedulingMonad, S](api: Gopher[F]): /** * FluentDSL for user SelectGroup without macroses. *``` - * SelectGroup.reading(input){ x => println(x) } - * .reading(endSignal){ () => done=true } + * SelectGroup.onRead(input){ x => println(x) } + * .onRead(endSignal){ () => done=true } *``` **/ - def reading[A](ch: ReadChannel[F,A]) (f: A => S ): this.type = + def onRead[A](ch: ReadChannel[F,A]) (f: A => S ): this.type = addReader[A](ch,{ case Success(a) => m.tryPure(f(a)) case Failure(ex) => m.error(ex) @@ -71,27 +71,49 @@ class SelectGroup[F[_]:CpsSchedulingMonad, S](api: Gopher[F]): this // reading call will be tranformed to reader_async in async expressions - def reading_async[A](ch: ReadChannel[F,A])(f: A => F[S] ): F[this.type] = + def onRead_async[A](ch: ReadChannel[F,A])(f: A => F[S] ): F[this.type] = addReader[A](ch,{ case Success(a) => m.tryImpure(f(a)) case Failure(ex) => m.error(ex) }) m.pure(this) - def writing[A](ch: WriteChannel[F,A], a:A)(f: A =>S ): SelectGroup[F,S] = + /** + * FluentDSL for user SelectGroup without macroses. + *``` + * SelectGroup.onWrite(input){ x => println(x) } + * .onWrite(endSignal){ () => done=true } + *``` + **/ + def onWrite[A](ch: WriteChannel[F,A], a:A)(f: A =>S ): SelectGroup[F,S] = addWriter[A](ch,a,{ case Success(()) => m.tryPure(f(a)) case Failure(ex) => m.error(ex) }) this - def writing_async[A](ch: WriteChannel[F,A], a:A) (f: A => F[S] ): F[this.type] = + def onWrite_async[A](ch: WriteChannel[F,A], a:A) (f: A => F[S] ): F[this.type] = addWriter[A](ch,a,{ case Success(()) => m.tryImpure(f(a)) case Failure(ex) => m.error(ex) }) m.pure(this) + + def onTimeout(t:FiniteDuration)(f: FiniteDuration => S): SelectGroup[F,S] = + setTimeout(t,{ + case Success(x) => m.tryPure(f(x)) + case Failure(ex) => m.error(ex) + }) + this + + def onTimeout_async(t:FiniteDuration)(f: FiniteDuration => F[S]): F[SelectGroup[F,S]] = + setTimeout(t,{ + case Success(x) => m.tryImpure(f(x)) + case Failure(ex) => m.error(ex) + }) + m.pure(this) + // trait Expiration: diff --git a/shared/src/test/scala/gopher/channels/FibonnachySimpleTests.scala b/shared/src/test/scala/gopher/channels/FibonnachySimpleTests.scala index 6efb9d66..e68e8e58 100644 --- a/shared/src/test/scala/gopher/channels/FibonnachySimpleTests.scala +++ b/shared/src/test/scala/gopher/channels/FibonnachySimpleTests.scala @@ -20,11 +20,11 @@ class FibbonachySimpleTest extends FunSuite { var done = false while(!done) { // TODO: add select group to given - select.group[Unit]().writing(c, x){ x0 => + select.group[Unit]().onWrite(c, x){ x0 => x=y y=x0+y } - .reading(quit){ v => + .onRead(quit){ v => done = true } .run From 43f66126a788e28405ebd2dc0cbe28c57ff0d3d8 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Fri, 11 Dec 2020 20:42:15 +0200 Subject: [PATCH 014/161] FlowTermination is out from new design --- .../main/scala/gopher/FlowTermination.scala | 33 ------------------- 1 file changed, 33 deletions(-) delete mode 100644 shared/src/main/scala/gopher/FlowTermination.scala diff --git a/shared/src/main/scala/gopher/FlowTermination.scala b/shared/src/main/scala/gopher/FlowTermination.scala deleted file mode 100644 index 53f2a02f..00000000 --- a/shared/src/main/scala/gopher/FlowTermination.scala +++ /dev/null @@ -1,33 +0,0 @@ -package gopher - -import scala.annotation._ - -sealed trait SelectFlow[S] - - - -/** - * FlowTermination- termination of flow. - * - * Inside each block in select loop or - * select apply (once or forever) we have implicit - * FlowTermination entity, which we can use for - * exiting the loop. - * - *{{{ - * select.forever{ - * case x: info.read => Console.println(s"received from info \$x") - * case x: control.read => Select.Done(()) - * } - *}}} - **/ -trait FlowTermination: - - /** - * terminate current flow and leave `a` as result of flow. - * have no effect if flow is already completed. - */ - def apply[A](value: A): A - - - From f7231b687fe456061d8fc017a1b8e2b9722976e2 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Sun, 13 Dec 2020 19:04:45 +0200 Subject: [PATCH 015/161] start to port old tests --- js/src/main/scala/gopher/JSGopher.scala | 18 ++- jvm/src/main/scala/gopher/JVMGopher.scala | 17 ++- .../scala/gopher/impl/PromiseChannel.scala | 107 ++++++++++++++++++ shared/src/main/scala/gopher/Gopher.scala | 16 ++- .../src/main/scala/gopher/ReadChannel.scala | 1 - shared/src/main/scala/gopher/Select.scala | 5 +- .../src/main/scala/gopher/SelectGroup.scala | 2 +- shared/src/main/scala/gopher/SelectLoop.scala | 67 +++++++++++ shared/src/main/scala/gopher/Time.scala | 42 ++++--- .../channels/FibonnachySimpleTests.scala | 4 +- .../channels/history}/AsyncSelectSuite.scala | 65 ++++++----- 11 files changed, 279 insertions(+), 65 deletions(-) create mode 100644 jvm/src/main/scala/gopher/impl/PromiseChannel.scala create mode 100644 shared/src/main/scala/gopher/SelectLoop.scala rename {0.99.x/src/test/scala/gopher/channels => shared/src/test/scala/gopher/channels/history}/AsyncSelectSuite.scala (70%) diff --git a/js/src/main/scala/gopher/JSGopher.scala b/js/src/main/scala/gopher/JSGopher.scala index afc6e392..ce4bde3c 100644 --- a/js/src/main/scala/gopher/JSGopher.scala +++ b/js/src/main/scala/gopher/JSGopher.scala @@ -2,15 +2,23 @@ package gopher import cps._ import java.util.Timer +import scala.concurrent.duration._ class JSGopher[F[_]:CpsSchedulingMonad](cfg: JSGopherConfig) extends Gopher[F]: - def makeChannel[A](bufSize:Int) = - if (bufSize == 1) then - impl.UnbufferedChannel[F,A](this) - else - impl.BufferedChannel[F,A](this,bufSize) + def makeChannel[A](bufSize:Int = 0, autoClose: Boolean = false, expire: Duration = Duration.Inf) = + if (expire == Duration.Inf ) + if (!autoClose) then + if (bufSize == 0) then + impl.UnbufferedChannel[F,A](this) + else + impl.BufferedChannel[F,A](this,bufSize) + else + ??? + //impl.PromiseChannel[F,A](this) + else + ??? def timer = JSGopher.timer diff --git a/jvm/src/main/scala/gopher/JVMGopher.scala b/jvm/src/main/scala/gopher/JVMGopher.scala index 75748dc6..9d5228ea 100644 --- a/jvm/src/main/scala/gopher/JVMGopher.scala +++ b/jvm/src/main/scala/gopher/JVMGopher.scala @@ -7,17 +7,24 @@ import java.util.concurrent.Executors import java.util.concurrent.ExecutorService import java.util.concurrent.ForkJoinPool import java.util.Timer +import scala.concurrent.duration._ class JVMGopher[F[_]:CpsSchedulingMonad](cfg: JVMGopherConfig) extends Gopher[F]: - def makeChannel[A](bufSize:Int) = - if (bufSize == 0) - GuardedSPSCUnbufferedChannel[F,A](this, cfg.controlExecutor,cfg.taskExecutor) - else - GuardedSPSCBufferedChannel[F,A](this, bufSize, cfg.controlExecutor,cfg.taskExecutor) + def makeChannel[A](bufSize:Int = 0, autoClose: Boolean = false, ttl: Duration = Duration.Inf) = + if (ttl == Duration.Inf) then + if autoClose then + PromiseChannel[F,A](this, taskExecutor) + else + if (bufSize == 0) + GuardedSPSCUnbufferedChannel[F,A](this, cfg.controlExecutor,cfg.taskExecutor) + else + GuardedSPSCBufferedChannel[F,A](this, bufSize, cfg.controlExecutor,cfg.taskExecutor) + else + ??? def timer = JVMGopher.timer diff --git a/jvm/src/main/scala/gopher/impl/PromiseChannel.scala b/jvm/src/main/scala/gopher/impl/PromiseChannel.scala new file mode 100644 index 00000000..9709b520 --- /dev/null +++ b/jvm/src/main/scala/gopher/impl/PromiseChannel.scala @@ -0,0 +1,107 @@ +package gopher.impl + +import cps._ +import gopher._ +import java.util.concurrent.ConcurrentLinkedDeque +import java.util.concurrent.ExecutorService +import java.util.concurrent.atomic.AtomicReference +import java.util.concurrent.atomic.AtomicInteger +import java.util.concurrent.atomic.AtomicBoolean +import scala.util.Try +import scala.util.Success +import scala.util.Failure + + +/** + * Channel is closed immediatly after successfull write. + **/ + class PromiseChannel[F[_]:CpsAsyncMonad,A](gopherApi: JVMGopher[F], taskExecutor: ExecutorService) extends Channel[F,A,A]: + + protected val readers = new ConcurrentLinkedDeque[Reader[A]]() + protected val ref: AtomicReference[AnyRef | Null] = new AtomicReference(null) + protected val closed: AtomicBoolean = new AtomicBoolean(false) + protected val readed: AtomicBoolean = new AtomicBoolean(false) + + protected override def asyncMonad = summon[CpsAsyncMonad[F]] + + def addReader(reader: Reader[A]): Unit = + if (ref.get() eq null) then + readers.add(reader) + step() + else + var done = false + while(!done && !reader.isExpired) { + reader.capture() match + case Some(f) => + f(Failure(new ChannelClosedException())) + done = true + case None => + if (!reader.isExpired) then + reader.markFree() + Thread.onSpinWait() + } + + def addWriter(writer: Writer[A]): Unit = + var done = false + while(!done && !writer.isExpired) + writer.capture() match + case Some((a,f)) => + val ar: AnyRef = a.asInstanceOf[AnyRef] // + if (ref.compareAndSet(null,ar) && !closed.get() ) then + closed.lazySet(true) + step() + done = true + else + f(Failure(new ChannelClosedException())) + done = true + case None => + if (!writer.isExpired) { + writer.markFree() + Thread.onSpinWait() + } + + def close(): Unit = + closed.set(true) + if (ref.get() eq null) + closeAll() + + + def step(): Unit = + val ar = ref.get() + if !(ar eq null) then + var done = false + while(!done && !readers.isEmpty) { + val r = readers.poll() + if ! (r eq null) then + while (!done && !r.isExpired) { + r.capture() match + case Some(f) => + done = true + if (readed.compareAndSet(false,true)) then + val a = ar.nn.asInstanceOf[A] + f(Success(a)) + else + f(Failure(new ChannelClosedException())) + case None => + if (!r.isExpired) { + if (readers.isEmpty) + Thread.onSpinWait() + readers.addLast(r) + } + } + } + else if (closed.get()) then + closeAll() + + def closeAll(): Unit = + while(!readers.isEmpty) + val r = readers.poll() + if (!(r eq null) && !r.isExpired) then + r.capture() match + case Some(f) => + f(Failure(new ChannelClosedException)) + case None => + if (!r.isExpired) then + if (readers.isEmpty) then + Thread.onSpinWait() + readers.addLast(r) diff --git a/shared/src/main/scala/gopher/Gopher.scala b/shared/src/main/scala/gopher/Gopher.scala index 80ae289b..752a689a 100644 --- a/shared/src/main/scala/gopher/Gopher.scala +++ b/shared/src/main/scala/gopher/Gopher.scala @@ -1,27 +1,35 @@ package gopher import cps._ +import scala.concurrent.duration.Duration import java.util.Timer + trait Gopher[F[_]:CpsSchedulingMonad]: type Monad[X] = F[X] def asyncMonad: CpsSchedulingMonad[F] = summon[CpsSchedulingMonad[F]] - def makeChannel[A](bufSize:Int = 0): Channel[F,A,A] + def makeChannel[A](bufSize:Int = 0, + autoClose: Boolean = false, + expire: Duration = Duration.Inf): Channel[F,A,A] + def makeOnceChannel[A](): Channel[F,A,A] = + makeChannel[A](1,true) + def select: Select[F] = new Select[F](this) def time: Time[F] = new Time[F](this) - def timer: Timer -def makeChannel[A](bufSize:Int = 1)(using g:Gopher[?]):Channel[g.Monad,A,A] = - g.makeChannel(bufSize) +def makeChannel[A](bufSize:Int = 0, + autoClose: Boolean = false, + expire: Duration = Duration.Inf)(using g:Gopher[?]):Channel[g.Monad,A,A] = + g.makeChannel(bufSize, autoClose, expire) def select(using g:Gopher[?]):Select[g.Monad] = g.select diff --git a/shared/src/main/scala/gopher/ReadChannel.scala b/shared/src/main/scala/gopher/ReadChannel.scala index 0e9a4514..b5d73cd0 100644 --- a/shared/src/main/scala/gopher/ReadChannel.scala +++ b/shared/src/main/scala/gopher/ReadChannel.scala @@ -16,7 +16,6 @@ trait ReadChannel[F[_], A]: protected def rAsyncMonad: CpsAsyncMonad[F] = asyncMonad def addReader(reader: Reader[A]): Unit - def aread:F[A] = asyncMonad.adoptCallbackStyle(f => addReader(SimpleReader(f))) diff --git a/shared/src/main/scala/gopher/Select.scala b/shared/src/main/scala/gopher/Select.scala index bc69a31e..ba23ed78 100644 --- a/shared/src/main/scala/gopher/Select.scala +++ b/shared/src/main/scala/gopher/Select.scala @@ -13,8 +13,9 @@ class Select[F[_]:CpsSchedulingMonad](api: Gopher[F]): Select.onceImpl[F,A]('pf, '{summonInline[CpsSchedulingMonad[F]]}, 'api ) } - def group[S](): SelectGroup[F,S] = new SelectGroup[F,S](api) + def group[S]: SelectGroup[F,S] = new SelectGroup[F,S](api) + def loop: SelectLoop[F] = new SelectLoop[F](api) object Select: @@ -61,7 +62,7 @@ object Select: val g: Expr[SelectGroup[F,S]] = cases.foldLeft(s0){(s,e) => parseCaseDef(e).appended(s) } - val r = '{ $g.run } + val r = '{ $g.run() } Term.of(r) diff --git a/shared/src/main/scala/gopher/SelectGroup.scala b/shared/src/main/scala/gopher/SelectGroup.scala index 17e7cd3c..4a651068 100644 --- a/shared/src/main/scala/gopher/SelectGroup.scala +++ b/shared/src/main/scala/gopher/SelectGroup.scala @@ -54,7 +54,7 @@ class SelectGroup[F[_]:CpsSchedulingMonad, S](api: Gopher[F]): def step():F[S] = retval - inline def run: S = await(step()) + inline def run(): S = await(step()) /** * FluentDSL for user SelectGroup without macroses. diff --git a/shared/src/main/scala/gopher/SelectLoop.scala b/shared/src/main/scala/gopher/SelectLoop.scala new file mode 100644 index 00000000..f600a67a --- /dev/null +++ b/shared/src/main/scala/gopher/SelectLoop.scala @@ -0,0 +1,67 @@ +package gopher + +import cps._ +import scala.concurrent.duration._ + +class SelectLoop[F[_]:CpsSchedulingMonad](api: Gopher[F]): + + private var groupBuilder: SelectGroup[F,Boolean] => SelectGroup[F,Boolean] = identity + + def onRead[A](ch: ReadChannel[F,A])(f: A => Boolean): this.type = + groupBuilder = groupBuilder.andThen{ + g => g.onRead(ch)(f) + } + this + + // TODO: think about special notation for builders + def onRead_async[A](ch: ReadChannel[F,A])(f: A => F[Boolean]): F[this.type] = + groupBuilder = groupBuilder.andThen{ + g => { + g.onRead_async(ch)(f) + g + } + } + summon[CpsMonad[F]].pure(this) + + + def onWrite[A](ch: WriteChannel[F,A], a: =>A)(f: A=>Boolean): this.type = + groupBuilder = groupBuilder.andThen{ + g => g.onWrite(ch,a)(f) + } + this + + def onWrite_async[A](ch: WriteChannel[F,A], a: =>A)(f: A=>F[Boolean]): F[this.type] = + groupBuilder = groupBuilder.andThen{ + g => + g.onWrite_async(ch,a)(f) + g + } + summon[CpsMonad[F]].pure(this) + + + def onTimeout(t: FiniteDuration)(f: FiniteDuration => Boolean): this.type = + groupBuilder = groupBuilder.andThen{ + g => g.onTimeout(t)(f) + } + this + + def onTimeout_async(t: FiniteDuration)(f: FiniteDuration => F[Boolean]): F[this.type] = + groupBuilder = groupBuilder.andThen{ + g => g.onTimeout_async(t)(f) + g + } + summon[CpsMonad[F]].pure(this) + + def runAsync(): F[Unit] = async[F] { + while{ + val group = api.select.group[Boolean] + groupBuilder(group).run() + } do () + } + + inline def run(): Unit = await(runAsync()) + + + + + diff --git a/shared/src/main/scala/gopher/Time.scala b/shared/src/main/scala/gopher/Time.scala index cfc34b4e..c8ad3b8c 100644 --- a/shared/src/main/scala/gopher/Time.scala +++ b/shared/src/main/scala/gopher/Time.scala @@ -8,7 +8,11 @@ import scala.concurrent.duration._ import java.util.concurrent.TimeUnit import scala.language.experimental.macros +import scala.util.Try import scala.util.Failure +import scala.util.Success +import java.util.concurrent.atomic.AtomicReference +import java.util.TimerTask /** * Time API, simular to one in golang standard library. @@ -19,26 +23,32 @@ class Time[F[_]](gopherAPI: Gopher[F]) { def after(duration: FiniteDuration): ReadChannel[F,FiniteDuration] = { - ??? - /* - val ch = OneTimeChannel.apply[Instant]()(gopherAPI) - gopherAPI.actorSystem.scheduler.scheduleOnce(duration){ - ch.awrite(Instant.now()) - }(ec) - ch - */ + val ch = gopherAPI.makeOnceChannel[FiniteDuration]() + gopherAPI.timer.schedule( + new TimerTask { + override def run() = { + val now = FiniteDuration(System.currentTimeMillis, TimeUnit.MILLISECONDS) + ch.awrite(now) + } + + }, + System.currentTimeMillis + duration.toMillis + ) + ch } def asleep(duration: FiniteDuration): F[FiniteDuration] = { - ??? - /* - val p = Promise[Instant]() - gopherAPI.actorSystem.scheduler.scheduleOnce(duration){ - p success Instant.now() - }(ec) - p.future - */ + var fun: Try[FiniteDuration] => Unit = _ => () + val retval = gopherAPI.asyncMonad.adoptCallbackStyle[FiniteDuration](listener => fun = listener) + gopherAPI.timer.schedule({ + new TimerTask { + override def run = + val now = FiniteDuration(System.currentTimeMillis, TimeUnit.MILLISECONDS) + fun(Success(now)) + } + }, System.currentTimeMillis + duration.toMillis) + retval } inline def sleep(duration: FiniteDuration): FiniteDuration = diff --git a/shared/src/test/scala/gopher/channels/FibonnachySimpleTests.scala b/shared/src/test/scala/gopher/channels/FibonnachySimpleTests.scala index e68e8e58..208bcac5 100644 --- a/shared/src/test/scala/gopher/channels/FibonnachySimpleTests.scala +++ b/shared/src/test/scala/gopher/channels/FibonnachySimpleTests.scala @@ -20,14 +20,14 @@ class FibbonachySimpleTest extends FunSuite { var done = false while(!done) { // TODO: add select group to given - select.group[Unit]().onWrite(c, x){ x0 => + select.group[Unit].onWrite(c, x){ x0 => x=y y=x0+y } .onRead(quit){ v => done = true } - .run + .run() } } diff --git a/0.99.x/src/test/scala/gopher/channels/AsyncSelectSuite.scala b/shared/src/test/scala/gopher/channels/history/AsyncSelectSuite.scala similarity index 70% rename from 0.99.x/src/test/scala/gopher/channels/AsyncSelectSuite.scala rename to shared/src/test/scala/gopher/channels/history/AsyncSelectSuite.scala index 0529a4be..0679af4a 100644 --- a/0.99.x/src/test/scala/gopher/channels/AsyncSelectSuite.scala +++ b/shared/src/test/scala/gopher/channels/history/AsyncSelectSuite.scala @@ -1,21 +1,30 @@ -package gopher.channels +package gopher.channels.history import gopher._ -import org.scalatest._ +import scala.concurrent._ +import cps.monads.FutureAsyncMonad -class AsyncSelectSuite extends AsyncFunSuite { +import munit._ + +class AsyncSelectSuite extends FunSuite { val MAX_N=100 + import scala.concurrent.ExecutionContext.Implicits.global + given Gopher[Future] = SharedGopherAPI.apply[Future]() + + test("async base: channel write, select read") { - val channel = gopherApi.makeChannel[Int](10) + val channel = makeChannel[Int](10) channel.awriteAll(1 to MAX_N) var sum = 0; - val consumer = gopherApi.select.loop.onRead(channel){ + /* + val consumer = gopherApi.select.loop.onRead(channel){ a + sum = sum + a (a:Int, cont:ContRead[Int,Unit]) => sum = sum + a if (a < MAX_N) { cont @@ -23,6 +32,12 @@ class AsyncSelectSuite extends AsyncFunSuite { Done((),cont.flowTermination) } }.go + */ + + val consumer = select.loop.onRead(channel){ a => + sum = sum + a + a < MAX_N + }.runAsync() //val consumer = go { // for(s <- select) { @@ -43,30 +58,21 @@ class AsyncSelectSuite extends AsyncFunSuite { } + test("async base: select write, select read") { - val channel = gopherApi.makeChannel[Int](10) + val channel = makeChannel[Int](10) var sum=0 var curA=0 - val process = gopherApi.select.loop. - onRead(channel){ - (a:Int, cont:ContRead[Int,Unit]) => sum = sum + a + val process = select.loop. + onRead(channel){ a => sum = sum + a //System.err.println("received:"+a) - if (a < MAX_N) { - cont - } else { - Done((),cont.flowTermination) - } - }.onWrite(channel){ - cont:ContWrite[Int,Unit] => - curA = curA+1 - if (curA < MAX_N) { - (curA, cont) - } else { - (curA,Done((),cont.flowTermination)) - } - }.go + a < MAX_N + }.onWrite(channel, curA){ a => + curA = curA + 1 + curA < MAX_N + }.runAsync() process map { _ => assert(curA == MAX_N) @@ -74,9 +80,10 @@ class AsyncSelectSuite extends AsyncFunSuite { } - test("async base: select read, default action") { +/* + test("async base: select read, timeout action") { - val channel = gopherApi.makeChannel[Int](10) + val channel = makeChannel[Int](10) val consumer = channel.atake(100) @@ -99,7 +106,9 @@ class AsyncSelectSuite extends AsyncFunSuite { rc <- consumer } yield assert(i > 100) } + */ + /* test("async base: catch exception in read") { val ERROR_N = 10 var lastReaded = 0 @@ -134,11 +143,9 @@ class AsyncSelectSuite extends AsyncFunSuite { } } + */ - - def actorSystem = CommonTestObjects.actorSystem - def gopherApi = CommonTestObjects.gopherApi - + } From d69074791fdf5d0078d3d08af8d32fa10482d745 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Tue, 15 Dec 2020 02:29:59 +0200 Subject: [PATCH 016/161] reimplemented Time, to use ScheduledExecitr on jvm --- js/src/main/scala/gopher/JSGopher.scala | 2 +- js/src/main/scala/gopher/impl/JSTime.scala | 61 ++++++++++++++++++ jvm/src/main/scala/gopher/JVMGopher.scala | 11 +++- .../main/scala/gopher/JVMGopherConfig.scala | 1 + jvm/src/main/scala/gopher/JVMTime.scala | 64 +++++++++++++++++++ shared/src/main/scala/gopher/Gopher.scala | 8 +-- shared/src/main/scala/gopher/GopherAPI.scala | 1 - .../src/main/scala/gopher/ReadChannel.scala | 17 +++++ .../src/main/scala/gopher/SelectGroup.scala | 23 +++---- shared/src/main/scala/gopher/Time.scala | 48 +++++++++----- .../channels/history/AsyncSelectSuite.scala | 42 ++++++------ 11 files changed, 219 insertions(+), 59 deletions(-) create mode 100644 js/src/main/scala/gopher/impl/JSTime.scala create mode 100644 jvm/src/main/scala/gopher/JVMTime.scala diff --git a/js/src/main/scala/gopher/JSGopher.scala b/js/src/main/scala/gopher/JSGopher.scala index ce4bde3c..612a4b06 100644 --- a/js/src/main/scala/gopher/JSGopher.scala +++ b/js/src/main/scala/gopher/JSGopher.scala @@ -20,7 +20,7 @@ class JSGopher[F[_]:CpsSchedulingMonad](cfg: JSGopherConfig) extends Gopher[F]: else ??? - def timer = JSGopher.timer + val time = new impl.JSTime(this) object JSGopher extends GopherAPI: diff --git a/js/src/main/scala/gopher/impl/JSTime.scala b/js/src/main/scala/gopher/impl/JSTime.scala new file mode 100644 index 00000000..9b288d39 --- /dev/null +++ b/js/src/main/scala/gopher/impl/JSTime.scala @@ -0,0 +1,61 @@ +package gopher.impl + +import gopher._ +import scala.concurrent.duration._ +import scala.collection.immutable.Queue +import scala.util._ + +import java.util.TimerTask + +class JSTime[F[_]](gopherAPI: JSGopher[F]) extends Time[F](gopherAPI): + + def schedule(fun:()=>Unit, delay: FiniteDuration): Time.Scheduled = + + var listeners: Queue[Try[Boolean]=>Unit] = Queue.empty + var canceled = false + + def notifyListeners(value: Try[Boolean]): Unit = + listeners.foreach{ f=> + try + f(value) + catch + case ex: Throwable => + ex.printStackTrace() + } + listeners = Queue.empty + + val task = new TimerTask { + override def run(): Unit = { + // TODO: log exception (?) + if (!canceled) then + try + fun() + catch + case ex: Throwable => + notifyListeners(Failure(ex)) + notifyListeners(Success(!canceled)) + } + } + + JSGopher.timer.schedule(task,delay.toMillis) + + + new Time.Scheduled { + + def cancel(): Boolean = + val r = task.cancel() + if (r) + notifyListeners(Success(false)) + r + + def onDone(f: Try[Boolean] => Unit) = + listeners = listeners.appended(f) + + } + + + + + + + diff --git a/jvm/src/main/scala/gopher/JVMGopher.scala b/jvm/src/main/scala/gopher/JVMGopher.scala index 9d5228ea..7759d3c8 100644 --- a/jvm/src/main/scala/gopher/JVMGopher.scala +++ b/jvm/src/main/scala/gopher/JVMGopher.scala @@ -26,10 +26,13 @@ class JVMGopher[F[_]:CpsSchedulingMonad](cfg: JVMGopherConfig) extends Gopher[F] else ??? - def timer = JVMGopher.timer + + val time = new JVMTime(this) def taskExecutor = cfg.taskExecutor + def scheduledExecutor = JVMGopher.scheduledExecutor + object JVMGopher extends GopherAPI: @@ -39,11 +42,13 @@ object JVMGopher extends GopherAPI: case DefaultGopherConfig => defaultConfig case jcfg:JVMGopherConfig => jcfg new JVMGopher[F](jvmConfig) - + lazy val timer = new Timer("gopher") + lazy val scheduledExecutor = Executors.newScheduledThreadPool(1) + lazy val defaultConfig=JVMGopherConfig( controlExecutor=Executors.newFixedThreadPool(2), - taskExecutor=ForkJoinPool.commonPool() + taskExecutor=ForkJoinPool.commonPool(), ) diff --git a/jvm/src/main/scala/gopher/JVMGopherConfig.scala b/jvm/src/main/scala/gopher/JVMGopherConfig.scala index cbe4ab26..0e01323e 100644 --- a/jvm/src/main/scala/gopher/JVMGopherConfig.scala +++ b/jvm/src/main/scala/gopher/JVMGopherConfig.scala @@ -1,6 +1,7 @@ package gopher import java.util.concurrent.ExecutorService +import java.util.concurrent.ScheduledExecutorService case class JVMGopherConfig( diff --git a/jvm/src/main/scala/gopher/JVMTime.scala b/jvm/src/main/scala/gopher/JVMTime.scala new file mode 100644 index 00000000..9b6d48fa --- /dev/null +++ b/jvm/src/main/scala/gopher/JVMTime.scala @@ -0,0 +1,64 @@ +package gopher + +import scala.concurrent.duration._ +import scala.util._ +import java.util.concurrent.TimeUnit +import java.util.concurrent.ScheduledFuture +import java.util.concurrent.ConcurrentLinkedQueue +import java.util.concurrent.atomic.AtomicBoolean + +class JVMTime[F[_]](gopherAPI: JVMGopher[F]) extends Time[F](gopherAPI) { + + def schedule(fun: () => Unit, delay: FiniteDuration): Time.Scheduled = + new JVMScheduled(fun,delay) + + class JVMScheduled(fun: ()=>Unit, delay: FiniteDuration) extends Time.Scheduled { + + val listeners = new ConcurrentLinkedQueue[Try[Boolean]=>Unit] + val cancelled = new AtomicBoolean(false) + + var wrapper = new Runnable() { + override def run(): Unit = + val doRun = !cancelled.get() + try { + if (doRun) { + fun() + } + } catch { + case ex: Throwable => + // TODO: set log. + notifyListeners(Failure(ex)) + } + notifyListeners(Success(doRun)) + } + + val jf = gopherAPI.scheduledExecutor.schedule(wrapper, delay.toMillis, TimeUnit.MILLISECONDS) + + def notifyListeners(value: Try[Boolean]): Unit = + while(! listeners.isEmpty) + val l = listeners.poll() + if (! (l eq null)) then + try + l.apply(value) + catch + case ex: Throwable => + // TODO: configure logging + ex.printStackTrace() + + + def cancel(): Boolean = + cancelled.set(true) + val r = jf.cancel(false) + if (r) then + notifyListeners(Success(false)) + r + + def onDone(listener: Try[Boolean] => Unit): Unit = + listeners.offer(listener) + + + + } + +} + diff --git a/shared/src/main/scala/gopher/Gopher.scala b/shared/src/main/scala/gopher/Gopher.scala index 752a689a..6655f1e8 100644 --- a/shared/src/main/scala/gopher/Gopher.scala +++ b/shared/src/main/scala/gopher/Gopher.scala @@ -2,7 +2,6 @@ package gopher import cps._ import scala.concurrent.duration.Duration -import java.util.Timer trait Gopher[F[_]:CpsSchedulingMonad]: @@ -16,15 +15,12 @@ trait Gopher[F[_]:CpsSchedulingMonad]: def makeOnceChannel[A](): Channel[F,A,A] = makeChannel[A](1,true) - + def select: Select[F] = new Select[F](this) - def time: Time[F] = new Time[F](this) + def time: Time[F] - def timer: Timer - - def makeChannel[A](bufSize:Int = 0, autoClose: Boolean = false, diff --git a/shared/src/main/scala/gopher/GopherAPI.scala b/shared/src/main/scala/gopher/GopherAPI.scala index 7d76aab2..8c88b0b2 100644 --- a/shared/src/main/scala/gopher/GopherAPI.scala +++ b/shared/src/main/scala/gopher/GopherAPI.scala @@ -35,7 +35,6 @@ object SharedGopherAPI { private[gopher] def setApi(api: GopherAPI): Unit = this._api = Some(api) - println("SharedGopherAPi") private[gopher] def initPlatformSpecific(): Unit = diff --git a/shared/src/main/scala/gopher/ReadChannel.scala b/shared/src/main/scala/gopher/ReadChannel.scala index b5d73cd0..34c251c8 100644 --- a/shared/src/main/scala/gopher/ReadChannel.scala +++ b/shared/src/main/scala/gopher/ReadChannel.scala @@ -24,6 +24,23 @@ trait ReadChannel[F[_], A]: inline def ? : A = await(aread)(using rAsyncMonad) + def atake(n: Int): F[IndexedSeq[A]] = + given CpsAsyncMonad[F] = asyncMonad + async[F]{ + var b = IndexedSeq.newBuilder[A] + try { + var c = 0 + while(c < n) { + val a = read + b.addOne(a) + c = c + 1 + } + }catch{ + case ex: ChannelClosedException => + } + b.result() + } + //object Read: // inline def unapply(): Option[A] = // Some(read) diff --git a/shared/src/main/scala/gopher/SelectGroup.scala b/shared/src/main/scala/gopher/SelectGroup.scala index 4a651068..a84ddfdb 100644 --- a/shared/src/main/scala/gopher/SelectGroup.scala +++ b/shared/src/main/scala/gopher/SelectGroup.scala @@ -25,7 +25,7 @@ class SelectGroup[F[_]:CpsSchedulingMonad, S](api: Gopher[F]): private inline def m = summon[CpsSchedulingMonad[F]] val retval = m.adoptCallbackStyle[S](f => call=f) val startTime = new AtomicLong(0L) - var timeoutTask: Option[TimerTask] = None + var timeoutScheduled: Option[Time.Scheduled] = None def addReader[A](ch: ReadChannel[F,A], action: Try[A]=>F[S]): Unit = val record = ReaderRecord(ch, action) @@ -36,19 +36,16 @@ class SelectGroup[F[_]:CpsSchedulingMonad, S](api: Gopher[F]): ch.addWriter(record) def setTimeout(timeout: FiniteDuration, action: Try[FiniteDuration] => F[S]): Unit = - timeoutTask.foreach(_.cancel()) - val newTask = new TimerTask() { - val record = new TimeoutRecord(timeout,action) - override def run(): Unit = { - val v = System.currentTimeMillis() - startTime.get() - record.capture() match + timeoutScheduled.foreach(_.cancel()) + val record = new TimeoutRecord(timeout,action) + val newTask = () => { + val v = System.currentTimeMillis() - startTime.get() + record.capture() match case Some(f) => f(Success(v milliseconds)) case None => // do nothing. - } } - timeoutTask = Some(newTask) - api.timer.schedule(newTask, System.currentTimeMillis() + timeout.toMillis); - + timeoutScheduled = Some(api.time.schedule(newTask,timeout)) + def step():F[S] = @@ -131,7 +128,7 @@ class SelectGroup[F[_]:CpsSchedulingMonad, S](api: Gopher[F]): override def capture(): Option[Try[A]=>Unit] = if waitState.compareAndSet(0,1) then Some(v => { - timeoutTask.foreach(_.cancel()) + timeoutScheduled.foreach(_.cancel()) m.spawn( m.mapTry(action(v))(x => call(x)) ) @@ -151,7 +148,7 @@ class SelectGroup[F[_]:CpsSchedulingMonad, S](api: Gopher[F]): override def capture(): Option[(A,Try[Unit]=>Unit)] = if waitState.compareAndSet(0,1) then Some((element, (v:Try[Unit]) => { - timeoutTask.foreach(_.cancel()) + timeoutScheduled.foreach(_.cancel()) m.spawn( m.mapTry(action(v))(x=>call(x)) )} diff --git a/shared/src/main/scala/gopher/Time.scala b/shared/src/main/scala/gopher/Time.scala index c8ad3b8c..f078759a 100644 --- a/shared/src/main/scala/gopher/Time.scala +++ b/shared/src/main/scala/gopher/Time.scala @@ -3,7 +3,7 @@ package gopher import cps._ -import scala.concurrent._ + import scala.concurrent.duration._ import java.util.concurrent.TimeUnit @@ -14,25 +14,22 @@ import scala.util.Success import java.util.concurrent.atomic.AtomicReference import java.util.TimerTask + /** * Time API, simular to one in golang standard library. * @see gopherApi#time */ -class Time[F[_]](gopherAPI: Gopher[F]) { +abstract class Time[F[_]](gopherAPI: Gopher[F]) { def after(duration: FiniteDuration): ReadChannel[F,FiniteDuration] = { val ch = gopherAPI.makeOnceChannel[FiniteDuration]() - gopherAPI.timer.schedule( - new TimerTask { - override def run() = { - val now = FiniteDuration(System.currentTimeMillis, TimeUnit.MILLISECONDS) - ch.awrite(now) - } - + schedule( () => { + val now = FiniteDuration(System.currentTimeMillis, TimeUnit.MILLISECONDS) + ch.awrite(now) }, - System.currentTimeMillis + duration.toMillis + duration ) ch } @@ -41,13 +38,11 @@ class Time[F[_]](gopherAPI: Gopher[F]) { { var fun: Try[FiniteDuration] => Unit = _ => () val retval = gopherAPI.asyncMonad.adoptCallbackStyle[FiniteDuration](listener => fun = listener) - gopherAPI.timer.schedule({ - new TimerTask { - override def run = - val now = FiniteDuration(System.currentTimeMillis, TimeUnit.MILLISECONDS) - fun(Success(now)) - } - }, System.currentTimeMillis + duration.toMillis) + schedule(() => { + val now = FiniteDuration(System.currentTimeMillis, TimeUnit.MILLISECONDS) + fun(Success(now)) + }, + duration) retval } @@ -98,6 +93,13 @@ class Time[F[_]](gopherAPI: Gopher[F]) { def now(): FiniteDuration = FiniteDuration(System.currentTimeMillis(),TimeUnit.MILLISECONDS) + + /** + * Low lwvel interface for scheduler + */ + def schedule(fun: () => Unit, delay: FiniteDuration): Time.Scheduled + + } object Time: @@ -118,3 +120,15 @@ object Time: def after[F[_]](duration: FiniteDuration)(using Gopher[F]): ReadChannel[F,FiniteDuration] = summon[Gopher[F]].time.after(duration) + + /** + * Task, which can be cancelled. + **/ + trait Scheduled { + + def cancel(): Boolean + + def onDone( listener: Try[Boolean]=>Unit ): Unit + + } + \ No newline at end of file diff --git a/shared/src/test/scala/gopher/channels/history/AsyncSelectSuite.scala b/shared/src/test/scala/gopher/channels/history/AsyncSelectSuite.scala index 0679af4a..c920c1b2 100644 --- a/shared/src/test/scala/gopher/channels/history/AsyncSelectSuite.scala +++ b/shared/src/test/scala/gopher/channels/history/AsyncSelectSuite.scala @@ -2,6 +2,9 @@ package gopher.channels.history import gopher._ import scala.concurrent._ +import scala.concurrent.duration._ +import scala.language.postfixOps +import scala.util._ import cps.monads.FutureAsyncMonad import munit._ @@ -80,7 +83,7 @@ class AsyncSelectSuite extends FunSuite { } -/* + test("async base: select read, timeout action") { val channel = makeChannel[Int](10) @@ -89,47 +92,50 @@ class AsyncSelectSuite extends FunSuite { var i = 1 var d = 1 - val process = gopherApi.select.loop[Int].onWrite(channel) { - cont:ContWrite[Int,Int] => i=i+1 - (i,cont) - }.onIdle{ - cont:Skip[Int] => + val process = select.loop.onWrite(channel, i) { a => + i=i+1 + i < 1000 + }.onTimeout(100 millisecond){ t => if (i < 100) { d=d+1 - cont + true } else { - Done(d,cont.flowTermination) + false } - }.go + }.runAsync() for{rp <- process rc <- consumer } yield assert(i > 100) } - */ + - /* test("async base: catch exception in read") { val ERROR_N = 10 var lastReaded = 0 - val channel = gopherApi.makeChannel[Int](10) - val process = gopherApi.select.loop. + val channel = makeChannel[Int](10) + val process = select.loop. onRead(channel){ - (a:Int, cont:ContRead[Int,Unit]) => lastReaded=a + (a:Int) => lastReaded=a if (a == ERROR_N) { throw new IllegalStateException("qqq") } - cont - }.go + true + }.runAsync() channel.awriteAll(1 to MAX_N) - recoverToSucceededIf[IllegalStateException]{ - process + process.transform{ + case Failure(ex: IllegalStateException) => + Success(assert(true)) + case Success(_) => + assert("" == "processs should failed wit IllegalStateException") + Success(()) } } + /* test("async base: catch exception in idle") { val process = gopherApi.select.loop.onIdle( (cont: Skip[Int]) => From 0fa28668400fe1a52395d956d213d4576e15cbca Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Tue, 15 Dec 2020 02:39:00 +0200 Subject: [PATCH 017/161] ported AsyncSelectTest --- .../channels/history/AsyncSelectSuite.scala | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/shared/src/test/scala/gopher/channels/history/AsyncSelectSuite.scala b/shared/src/test/scala/gopher/channels/history/AsyncSelectSuite.scala index c920c1b2..474eb07c 100644 --- a/shared/src/test/scala/gopher/channels/history/AsyncSelectSuite.scala +++ b/shared/src/test/scala/gopher/channels/history/AsyncSelectSuite.scala @@ -130,26 +130,26 @@ class AsyncSelectSuite extends FunSuite { Success(assert(true)) case Success(_) => assert("" == "processs should failed wit IllegalStateException") - Success(()) + Failure(new RuntimeException("fail")) } } - /* test("async base: catch exception in idle") { - val process = gopherApi.select.loop.onIdle( - (cont: Skip[Int]) => - if (true) { + val process = select.loop.onTimeout(100 milliseconds)( + t => throw new IllegalStateException("qqq") - } else cont - ).go + ).runAsync() - recoverToSucceededIf[IllegalStateException]{ - process + process.transform{ + case Failure(ex: IllegalStateException) => + Success(assert(true)) + case Success(_) => + assert("" == "processs should failed wit IllegalStateException") + Failure(new RuntimeException("fail")) } } - */ From 3f21a09ab030b3c3db7dcb0742cb2f16b4d4ce2b Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Tue, 15 Dec 2020 10:46:42 +0200 Subject: [PATCH 018/161] ported test-suite in progress --- .../gopher/impl/GuardedSPSCBaseChannel.scala | 43 ++++++++++++--- .../impl/GuardedSPSCBufferedChannel.scala | 12 ++--- .../impl/GuardedSPSCUnbufferedChannel.scala | 21 +++++--- .../gopher/channels/ChannelCloseSuite.scala | 54 +++++++++++++++---- 4 files changed, 96 insertions(+), 34 deletions(-) rename {0.99.x => shared}/src/test/scala/gopher/channels/ChannelCloseSuite.scala (50%) diff --git a/jvm/src/main/scala/gopher/impl/GuardedSPSCBaseChannel.scala b/jvm/src/main/scala/gopher/impl/GuardedSPSCBaseChannel.scala index 68e68f76..8f17444c 100644 --- a/jvm/src/main/scala/gopher/impl/GuardedSPSCBaseChannel.scala +++ b/jvm/src/main/scala/gopher/impl/GuardedSPSCBaseChannel.scala @@ -44,9 +44,12 @@ abstract class GuardedSPSCBaseChannel[F[_]:CpsAsyncMonad,A](gopherApi: JVMGopher def addWriter(writer: Writer[A]): Unit = if (writer.canExpire) then - writers.removeIf( _.isExpired ) - writers.add(writer) - controlExecutor.submit(stepRunnable) + writers.removeIf( _.isExpired ) + if (publishedClosed.get()) then + closeWriter(writer) + else + writers.add(writer) + controlExecutor.submit(stepRunnable) def close(): Unit = publishedClosed.set(true) @@ -96,9 +99,7 @@ abstract class GuardedSPSCBaseChannel[F[_]:CpsAsyncMonad,A](gopherApi: JVMGopher taskExecutor.execute(() => f(Failure(new ChannelClosedException())) ) case None => progress = true - Thread.onSpinWait() - if (!r.isExpired ) then - readers.addLast(r) + progressWaitReader(r) } progress @@ -113,11 +114,37 @@ abstract class GuardedSPSCBaseChannel[F[_]:CpsAsyncMonad,A](gopherApi: JVMGopher taskExecutor.execute(() => f(Failure(new ChannelClosedException)) ) case None => progress = true - if (!w.isExpired) then - writers.addLast(w) + progressWaitWriter(w) } progress + protected def closeWriter(w: Writer[A]): Unit = { + var done = false + while (!done && !w.isExpired) + w.capture() match + case Some((a,f)) => + taskExecutor.execute(() => f(Failure(new ChannelClosedException)) ) + done = true + case None => + if (!w.isExpired) then + Thread.onSpinWait() + } + + // precondition: r.capture() == None + protected def progressWaitReader(r: Reader[A]): Unit = + if (!r.isExpired) + if (readers.isEmpty) + Thread.onSpinWait() + readers.addLast(r) + + // precondition: w.capture() == None + protected def progressWaitWriter(w: Writer[A]): Unit = + if (!w.isExpired) + if (writers.isEmpty) + Thread.onSpinWait() + writers.addLast(w) + + object GuardedSPSCBaseChannel: diff --git a/jvm/src/main/scala/gopher/impl/GuardedSPSCBufferedChannel.scala b/jvm/src/main/scala/gopher/impl/GuardedSPSCBufferedChannel.scala index 1737e3bb..534e6905 100644 --- a/jvm/src/main/scala/gopher/impl/GuardedSPSCBufferedChannel.scala +++ b/jvm/src/main/scala/gopher/impl/GuardedSPSCBufferedChannel.scala @@ -85,7 +85,7 @@ taskExecutor: ExecutorService) extends GuardedSPSCBaseChannel[F,A](gopherApi,con else if isClosed then progress |= processReadClose() - if (!state.isFull()) then + if (!state.isFull() && !isClosed) then progress |= processWriteStep() if (isClosed) progress |= processWriteClose() @@ -93,6 +93,7 @@ taskExecutor: ExecutorService) extends GuardedSPSCBaseChannel[F,A](gopherApi,con state.publish() if (! checkLeaveStep()) { progress = true + isClosed = publishedClosed.get() } } } @@ -124,11 +125,7 @@ taskExecutor: ExecutorService) extends GuardedSPSCBaseChannel[F,A](gopherApi,con // not in this thread, but progress. progress = true val (r, c) = nonExpiredBusyReads.dequeue - if (!r.isExpired) { - readers.addLast(r) - //TOOD: check lack of other progress ?? - //Thread.onSpinWait() - } + progressWaitReader(r) nonExpiredBusyReads = c } progress @@ -160,8 +157,7 @@ taskExecutor: ExecutorService) extends GuardedSPSCBaseChannel[F,A](gopherApi,con progress = true val (w, c) = nonExpiredBusyWriters.dequeue nonExpiredBusyWriters = c - if (! w.isExpired) then - writers.addLast(w) + progressWaitWriter(w) } progress diff --git a/jvm/src/main/scala/gopher/impl/GuardedSPSCUnbufferedChannel.scala b/jvm/src/main/scala/gopher/impl/GuardedSPSCUnbufferedChannel.scala index 05ffbb68..267c9375 100644 --- a/jvm/src/main/scala/gopher/impl/GuardedSPSCUnbufferedChannel.scala +++ b/jvm/src/main/scala/gopher/impl/GuardedSPSCUnbufferedChannel.scala @@ -20,6 +20,7 @@ class GuardedSPSCUnbufferedChannel[F[_]:CpsAsyncMonad,A]( protected override def step(): Unit = { var progress = true + var isClosed = publishedClosed.get() while (progress) { var readerLoopDone = false progress = false @@ -46,22 +47,28 @@ class GuardedSPSCUnbufferedChannel[F[_]:CpsAsyncMonad,A]( // reader same, other writer reader.markFree() progress = true // return when - if (!writer.isExpired) - writers.addLast(writer) - Thread.onSpinWait() + progressWaitWriter(writer) case None => writers.addFirst(writer) writersLoopDone = true progress = true - if (!reader.isExpired) - readers.addLast(reader) - Thread.onSpinWait() - } + progressWaitReader(reader) + } } + if (isClosed && (readers.isEmpty || writers.isEmpty) ) then + if (!writers.isEmpty) { + progress |= processWriteClose() + } + if (readers.isEmpty) { + progress |= processReadClose() + } if (!progress) then if !checkLeaveStep() then progress = true + isClosed = publishedClosed.get() } } + + diff --git a/0.99.x/src/test/scala/gopher/channels/ChannelCloseSuite.scala b/shared/src/test/scala/gopher/channels/ChannelCloseSuite.scala similarity index 50% rename from 0.99.x/src/test/scala/gopher/channels/ChannelCloseSuite.scala rename to shared/src/test/scala/gopher/channels/ChannelCloseSuite.scala index 7cc8a9ca..06e894c2 100644 --- a/0.99.x/src/test/scala/gopher/channels/ChannelCloseSuite.scala +++ b/shared/src/test/scala/gopher/channels/ChannelCloseSuite.scala @@ -1,33 +1,62 @@ package gopher.channels - import gopher._ -import org.scalatest._ +import scala.concurrent.Future +import scala.util.Try +import scala.util.Success +import scala.util.Failure + +import cps._ +import cps.monads.FutureAsyncMonad -import scala.async.Async._ +import munit._ -class ChannelCloseSuite extends AsyncFunSuite +class ChannelCloseSuite extends FunSuite { + import scala.concurrent.ExecutionContext.Implicits.global + given Gopher[Future] = SharedGopherAPI.apply[Future]() test("writing after close is impossile") { - val channel = gopherApi.makeChannel[Int](100) + val channel = makeChannel[Int](100) - channel.close + channel.close() val producer = channel.awriteAll(1 to 1000) - recoverToSucceededIf[ChannelClosedException] { - producer + producer.transform{ + case Success(u) => assert("" == "expected ChannelClosedException") + Failure(RuntimeException("fail")) + case Failure(ex) => assert(ex.isInstanceOf[ChannelClosedException]) + Success("ok") } } + def checkThrowWhenWriteClose(name: String, channel: gopher.Channel[Future,Int,Int])(implicit loc:munit.Location)= { + test(s"in async we must see throw for $name") { + channel.close() + @volatile var catched = false + @volatile var notCatched = false + val p = async { + channel.write(1) + notCatched=true + } + p.recover{ + case ex: ChannelClosedException => catched = true + }.map(_ => assert(!notCatched && catched)) + } + } + + checkThrowWhenWriteClose("buffered", makeChannel[Int](100)) + checkThrowWhenWriteClose("unbuffered", makeChannel[Int]()) + + /* test("in async we must see throw") { - val channel = gopherApi.makeChannel[Int](100) - channel.close + val channel = makeChannel[Int](100) + channel.close() @volatile var catched = false @volatile var notCatched = false val p = async { @@ -39,7 +68,9 @@ class ChannelCloseSuite extends AsyncFunSuite }.map(_ => assert(!notCatched && catched)) } + */ + /* test("after close we can read but not more, than was send") { val channel = gopherApi.makeChannel[Int](100) @volatile var q1, q2 = 0 @@ -76,8 +107,9 @@ class ChannelCloseSuite extends AsyncFunSuite ) } +*/ - def gopherApi = CommonTestObjects.gopherApi +// def gopherApi = CommonTestObjects.gopherApi } From c27a3f575dc1955124b2514e4bd7584854fdd3f5 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Thu, 17 Dec 2020 22:54:07 +0200 Subject: [PATCH 019/161] implemtned done, ported CloseChannelSuite from 0.99 --- .../main/scala/gopher/DeadlockDetected.scala | 4 + js/src/main/scala/gopher/JSGopher.scala | 3 +- .../main/scala/gopher/impl/BaseChannel.scala | 49 +++++++- .../scala/gopher/impl/BufferedChannel.scala | 2 + .../scala/gopher/impl/PromiseChannel.scala | 57 +++++++++ .../scala/gopher/impl/UnbufferedChannel.scala | 10 +- .../gopher/impl/GuardedSPSCBaseChannel.scala | 59 ++++++--- .../impl/GuardedSPSCBufferedChannel.scala | 1 + .../impl/GuardedSPSCUnbufferedChannel.scala | 13 +- .../scala/gopher/impl/PromiseChannel.scala | 48 ++++++-- shared/src/main/scala/gopher/Gopher.scala | 4 + .../src/main/scala/gopher/ReadChannel.scala | 59 ++++++++- .../gopher/channels/ChannelCloseSuite.scala | 114 ++++++++++++------ 13 files changed, 342 insertions(+), 81 deletions(-) create mode 100644 js/src/main/scala/gopher/DeadlockDetected.scala create mode 100644 js/src/main/scala/gopher/impl/PromiseChannel.scala diff --git a/js/src/main/scala/gopher/DeadlockDetected.scala b/js/src/main/scala/gopher/DeadlockDetected.scala new file mode 100644 index 00000000..5bd8a805 --- /dev/null +++ b/js/src/main/scala/gopher/DeadlockDetected.scala @@ -0,0 +1,4 @@ +package gopher + +class DeadlockDetected extends RuntimeException("Deadlock detected") + \ No newline at end of file diff --git a/js/src/main/scala/gopher/JSGopher.scala b/js/src/main/scala/gopher/JSGopher.scala index 612a4b06..103383af 100644 --- a/js/src/main/scala/gopher/JSGopher.scala +++ b/js/src/main/scala/gopher/JSGopher.scala @@ -15,8 +15,7 @@ class JSGopher[F[_]:CpsSchedulingMonad](cfg: JSGopherConfig) extends Gopher[F]: else impl.BufferedChannel[F,A](this,bufSize) else - ??? - //impl.PromiseChannel[F,A](this) + impl.PromiseChannel[F,A](this) else ??? diff --git a/js/src/main/scala/gopher/impl/BaseChannel.scala b/js/src/main/scala/gopher/impl/BaseChannel.scala index a19d7837..ddddd189 100644 --- a/js/src/main/scala/gopher/impl/BaseChannel.scala +++ b/js/src/main/scala/gopher/impl/BaseChannel.scala @@ -12,14 +12,15 @@ abstract class BaseChannel[F[_]:CpsAsyncMonad,A](root: JSGopher[F]) extends Chan protected val readers: Queue[Reader[A]] = Queue.empty protected val writers: Queue[Writer[A]] = Queue.empty + protected val doneReaders: Queue[Reader[Unit]] = Queue.empty protected var closed: Boolean = false protected override def asyncMonad: cps.CpsAsyncMonad[F] = summon[CpsAsyncMonad[F]] - override def close(): Unit = closed = true + processClose() protected def submitTask(f: ()=>Unit ): Unit = JSExecutionContext.queue.execute{ () => @@ -34,7 +35,7 @@ abstract class BaseChannel[F[_]:CpsAsyncMonad,A](root: JSGopher[F]) extends Chan } def addReader(reader: Reader[A]): Unit = - if (closed && writers.isEmpty ) { + if (closed && isEmpty ) { reader.capture().foreach{ f => submitTask( () => f(Failure(new ChannelClosedException())) @@ -56,7 +57,51 @@ abstract class BaseChannel[F[_]:CpsAsyncMonad,A](root: JSGopher[F]) extends Chan writers.enqueue(writer) process() } + + def addDoneReader(reader: Reader[Unit]): Unit = + if (closed && isEmpty) { + reader.capture().foreach{ f => + submitTask( () => f(Success(()))) + } + } else { + doneReaders.enqueue(reader) + } + + protected def processClose(): Unit = + if (isEmpty) then + processCloseDone() + submitTask(processCloseWriters) + submitTask(processCloseReaders) + + + protected def processCloseDone(): Unit = + val success = Success(()) + doneReaders.foreach( reader => + reader.capture().foreach{ f => + f(success) + } + ) + + protected def processCloseReaders(): Unit = + val channelClosed = Failure(ChannelClosedException()) + readers.foreach{ reader => + reader.capture().foreach{ f => + f(channelClosed) + } + } + + protected def processCloseWriters(): Unit = + val channelClosed = Failure(ChannelClosedException()) + writers.foreach{ writer => + writer.capture().foreach{ (a,f) => + f(channelClosed) + } + } + + + protected def isEmpty: Boolean protected def process(): Unit + diff --git a/js/src/main/scala/gopher/impl/BufferedChannel.scala b/js/src/main/scala/gopher/impl/BufferedChannel.scala index 3aabcd40..fe2134a0 100644 --- a/js/src/main/scala/gopher/impl/BufferedChannel.scala +++ b/js/src/main/scala/gopher/impl/BufferedChannel.scala @@ -51,6 +51,8 @@ class BufferedChannel[F[_]:CpsAsyncMonad, A](gopherApi: JSGopher[F], bufSize: In case None => // nothing. progress |= processWriters() + if (closed) then + processClose() protected def processReaders(a:A): Boolean = var progress = false diff --git a/js/src/main/scala/gopher/impl/PromiseChannel.scala b/js/src/main/scala/gopher/impl/PromiseChannel.scala new file mode 100644 index 00000000..710a8d5f --- /dev/null +++ b/js/src/main/scala/gopher/impl/PromiseChannel.scala @@ -0,0 +1,57 @@ +package gopher.impl + +import cps._ +import gopher._ +import scala.collection.mutable.Queue +import scala.scalajs.concurrent.JSExecutionContext +import scala.util._ +import scala.util.control.NonFatal + +class PromiseChannel[F[_]:CpsAsyncMonad, A](gopherApi: JSGopher[F]) extends BaseChannel[F,A](gopherApi): + + private var value: Option[A] = None + + protected def isEmpty: Boolean = value.isEmpty + + protected def process(): Unit = + var done = false + // we have only one writer. + while (!writers.isEmpty && value.isEmpty) { + val w = writers.dequeue() + if (!w.isExpired) then + w.capture() match + case Some((a,f)) => + submitTask(()=>f(Success(()))) + value = Some(a) + closed = true + // we can't havw more than one unexpired + case None => + if (!w.isExpired) then + // impossible in js, + throw new DeadlockDetected() + } + if (!readers.isEmpty && value.isDefined) { + var readed = false + while(!readers.isEmpty) { + val r = readers.dequeue() + if (!r.isExpired) then + r.capture() match + case Some(f) => + if (!readed) then + submitTask(()=>f(Success(value.get))) + readed = true + else + processCloseDone() // to have .done call befor channel-closed. + submitTask(()=>f(Failure(ChannelClosedException()))) + case None => + if (!r.isExpired) + throw new DeadlockDetected() + } + } + if (closed) then + processClose() + + + + + \ No newline at end of file diff --git a/js/src/main/scala/gopher/impl/UnbufferedChannel.scala b/js/src/main/scala/gopher/impl/UnbufferedChannel.scala index 25072ae4..81cb4d5f 100644 --- a/js/src/main/scala/gopher/impl/UnbufferedChannel.scala +++ b/js/src/main/scala/gopher/impl/UnbufferedChannel.scala @@ -10,6 +10,10 @@ import scala.util.control.NonFatal class UnbufferedChannel[F[_]:CpsAsyncMonad, A](gopherApi: JSGopher[F]) extends BaseChannel[F,A](gopherApi): + + protected def isEmpty: Boolean = + writers.isEmpty + protected def process(): Unit = var progress = true while(progress) { @@ -41,6 +45,9 @@ class UnbufferedChannel[F[_]:CpsAsyncMonad, A](gopherApi: JSGopher[F]) extends B done = true } } + if (closed) { + processClose() + } private def findUnexpired[T <: Expirable[?]](q: Queue[T]): Option[T] = @@ -58,4 +65,5 @@ class UnbufferedChannel[F[_]:CpsAsyncMonad, A](gopherApi: JSGopher[F]) extends B private def findWriter(): Option[Writer[A]] = findUnexpired(writers) - \ No newline at end of file + + \ No newline at end of file diff --git a/jvm/src/main/scala/gopher/impl/GuardedSPSCBaseChannel.scala b/jvm/src/main/scala/gopher/impl/GuardedSPSCBaseChannel.scala index 8f17444c..025ff939 100644 --- a/jvm/src/main/scala/gopher/impl/GuardedSPSCBaseChannel.scala +++ b/jvm/src/main/scala/gopher/impl/GuardedSPSCBaseChannel.scala @@ -26,6 +26,7 @@ abstract class GuardedSPSCBaseChannel[F[_]:CpsAsyncMonad,A](gopherApi: JVMGopher protected val readers = new ConcurrentLinkedDeque[Reader[A]]() protected val writers = new ConcurrentLinkedDeque[Writer[A]]() + protected val doneReaders = new ConcurrentLinkedDeque[Reader[Unit]]() protected val publishedClosed = new AtomicBoolean(false) @@ -43,14 +44,18 @@ abstract class GuardedSPSCBaseChannel[F[_]:CpsAsyncMonad,A](gopherApi: JVMGopher controlExecutor.submit(stepRunnable) def addWriter(writer: Writer[A]): Unit = - if (writer.canExpire) then - writers.removeIf( _.isExpired ) - if (publishedClosed.get()) then - closeWriter(writer) - else - writers.add(writer) - controlExecutor.submit(stepRunnable) - + if (writer.canExpire) then + writers.removeIf( _.isExpired ) + if (publishedClosed.get()) then + closeWriter(writer) + else + writers.add(writer) + controlExecutor.submit(stepRunnable) + + def addDoneReader(reader: Reader[Unit]): Unit = + doneReaders.add(reader) + controlExecutor.submit(stepRunnable) + def close(): Unit = publishedClosed.set(true) controlExecutor.submit(stepRunnable) @@ -118,6 +123,22 @@ abstract class GuardedSPSCBaseChannel[F[_]:CpsAsyncMonad,A](gopherApi: JVMGopher } progress + protected def processDoneClose(): Boolean = { + var progress = false + while(!doneReaders.isEmpty) { + val r = doneReaders.poll() + if !(r eq null) && !r.isExpired then + r.capture() match + case Some(f) => + progress = true + taskExecutor.execute(() => f(Success(()))) + case None => + progressWaitDoneReader(r) + } + progress + } + + protected def closeWriter(w: Writer[A]): Unit = { var done = false while (!done && !w.isExpired) @@ -130,20 +151,24 @@ abstract class GuardedSPSCBaseChannel[F[_]:CpsAsyncMonad,A](gopherApi: JVMGopher Thread.onSpinWait() } + + // precondition: r.capture() == None protected def progressWaitReader(r: Reader[A]): Unit = - if (!r.isExpired) - if (readers.isEmpty) - Thread.onSpinWait() - readers.addLast(r) + progressWait(r,readers) - // precondition: w.capture() == None + // precondition: w.capture() == None protected def progressWaitWriter(w: Writer[A]): Unit = - if (!w.isExpired) - if (writers.isEmpty) - Thread.onSpinWait() - writers.addLast(w) + progressWait(w,writers) + protected def progressWaitDoneReader(r: Reader[Unit]): Unit = + progressWait(r,doneReaders) + + protected def progressWait[T <: Expirable[_]](v:T, queue: ConcurrentLinkedDeque[T]): Unit = + if (!v.isExpired) + if (queue.isEmpty) + Thread.onSpinWait() + queue.addLast(v) object GuardedSPSCBaseChannel: diff --git a/jvm/src/main/scala/gopher/impl/GuardedSPSCBufferedChannel.scala b/jvm/src/main/scala/gopher/impl/GuardedSPSCBufferedChannel.scala index 534e6905..4d605f21 100644 --- a/jvm/src/main/scala/gopher/impl/GuardedSPSCBufferedChannel.scala +++ b/jvm/src/main/scala/gopher/impl/GuardedSPSCBufferedChannel.scala @@ -84,6 +84,7 @@ taskExecutor: ExecutorService) extends GuardedSPSCBaseChannel[F,A](gopherApi,con progress |= processReadsStep() else if isClosed then + progress |= processDoneClose() progress |= processReadClose() if (!state.isFull() && !isClosed) then progress |= processWriteStep() diff --git a/jvm/src/main/scala/gopher/impl/GuardedSPSCUnbufferedChannel.scala b/jvm/src/main/scala/gopher/impl/GuardedSPSCUnbufferedChannel.scala index 267c9375..ad310851 100644 --- a/jvm/src/main/scala/gopher/impl/GuardedSPSCUnbufferedChannel.scala +++ b/jvm/src/main/scala/gopher/impl/GuardedSPSCUnbufferedChannel.scala @@ -56,12 +56,13 @@ class GuardedSPSCUnbufferedChannel[F[_]:CpsAsyncMonad,A]( } } if (isClosed && (readers.isEmpty || writers.isEmpty) ) then - if (!writers.isEmpty) { - progress |= processWriteClose() - } - if (readers.isEmpty) { - progress |= processReadClose() - } + progress |= ( + processWriteClose() + || + processReadClose() + || + processDoneClose() + ) if (!progress) then if !checkLeaveStep() then progress = true diff --git a/jvm/src/main/scala/gopher/impl/PromiseChannel.scala b/jvm/src/main/scala/gopher/impl/PromiseChannel.scala index 9709b520..2b55c345 100644 --- a/jvm/src/main/scala/gopher/impl/PromiseChannel.scala +++ b/jvm/src/main/scala/gopher/impl/PromiseChannel.scala @@ -18,6 +18,7 @@ import scala.util.Failure class PromiseChannel[F[_]:CpsAsyncMonad,A](gopherApi: JVMGopher[F], taskExecutor: ExecutorService) extends Channel[F,A,A]: protected val readers = new ConcurrentLinkedDeque[Reader[A]]() + protected val doneReaders = new ConcurrentLinkedDeque[Reader[Unit]]() protected val ref: AtomicReference[AnyRef | Null] = new AtomicReference(null) protected val closed: AtomicBoolean = new AtomicBoolean(false) protected val readed: AtomicBoolean = new AtomicBoolean(false) @@ -33,7 +34,7 @@ import scala.util.Failure while(!done && !reader.isExpired) { reader.capture() match case Some(f) => - f(Failure(new ChannelClosedException())) + taskExecutor.execute(() => f(Failure(new ChannelClosedException()))) done = true case None => if (!reader.isExpired) then @@ -52,7 +53,7 @@ import scala.util.Failure step() done = true else - f(Failure(new ChannelClosedException())) + taskExecutor.execute(() => f(Failure(new ChannelClosedException()))) done = true case None => if (!writer.isExpired) { @@ -60,11 +61,29 @@ import scala.util.Failure Thread.onSpinWait() } + def addDoneReader(reader: Reader[Unit]): Unit = + if (!closed.get()) then + doneReaders.add(reader) + else + var done = false + while(!done & !reader.isExpired) { + reader.capture() match + case Some(f) => + taskExecutor.execute(()=>f(Success(()))) + done = true + case None => + if (!reader.isExpired) + Thread.onSpinWait() + } + + + def close(): Unit = closed.set(true) if (ref.get() eq null) closeAll() - + + def step(): Unit = val ar = ref.get() @@ -79,9 +98,9 @@ import scala.util.Failure done = true if (readed.compareAndSet(false,true)) then val a = ar.nn.asInstanceOf[A] - f(Success(a)) + taskExecutor.execute(() => f(Success(a))) else - f(Failure(new ChannelClosedException())) + taskExecutor.execute(() => f(Failure(new ChannelClosedException()))) case None => if (!r.isExpired) { if (readers.isEmpty) @@ -94,14 +113,29 @@ import scala.util.Failure closeAll() def closeAll(): Unit = - while(!readers.isEmpty) + while(!readers.isEmpty) { val r = readers.poll() if (!(r eq null) && !r.isExpired) then r.capture() match case Some(f) => - f(Failure(new ChannelClosedException)) + taskExecutor.execute(() => f(Failure(new ChannelClosedException))) case None => if (!r.isExpired) then if (readers.isEmpty) then Thread.onSpinWait() readers.addLast(r) + } + while(!doneReaders.isEmpty) { + val r = doneReaders.poll() + if !((r eq null) || r.isExpired) then + r.capture() match + case Some(f) => + taskExecutor.execute(()=>f(Success(()))) + case None => + if (!r.isExpired) then + if (doneReaders.isEmpty) then + Thread.onSpinWait() + doneReaders.addLast(r) + } + + diff --git a/shared/src/main/scala/gopher/Gopher.scala b/shared/src/main/scala/gopher/Gopher.scala index 6655f1e8..348500d4 100644 --- a/shared/src/main/scala/gopher/Gopher.scala +++ b/shared/src/main/scala/gopher/Gopher.scala @@ -27,6 +27,10 @@ def makeChannel[A](bufSize:Int = 0, expire: Duration = Duration.Inf)(using g:Gopher[?]):Channel[g.Monad,A,A] = g.makeChannel(bufSize, autoClose, expire) +def makeOnceChannel[A]()(using g:Gopher[?]): Channel[g.Monad,A,A] = + g.makeOnceChannel[A]() + + def select(using g:Gopher[?]):Select[g.Monad] = g.select diff --git a/shared/src/main/scala/gopher/ReadChannel.scala b/shared/src/main/scala/gopher/ReadChannel.scala index 34c251c8..684e6fd3 100644 --- a/shared/src/main/scala/gopher/ReadChannel.scala +++ b/shared/src/main/scala/gopher/ReadChannel.scala @@ -8,6 +8,8 @@ import scala.util.Failure trait ReadChannel[F[_], A]: + thisReadChannel => + type read = A // workarround for https://github.com/lampepfl/dotty/issues/10477 @@ -16,7 +18,17 @@ trait ReadChannel[F[_], A]: protected def rAsyncMonad: CpsAsyncMonad[F] = asyncMonad def addReader(reader: Reader[A]): Unit - + + def addDoneReader(reader: Reader[Unit]): Unit + + lazy val done: ReadChannel[F,Unit] = DoneReadChannel() + + type done = Unit + + /** + * async version of read. Immediatly return future, which will contains result of read or failur with StreamClosedException + * in case of stream is closed. + */ def aread:F[A] = asyncMonad.adoptCallbackStyle(f => addReader(SimpleReader(f))) @@ -24,6 +36,9 @@ trait ReadChannel[F[_], A]: inline def ? : A = await(aread)(using rAsyncMonad) + /** + * return F which contains sequence from first `n` elements. + */ def atake(n: Int): F[IndexedSeq[A]] = given CpsAsyncMonad[F] = asyncMonad async[F]{ @@ -39,11 +54,7 @@ trait ReadChannel[F[_], A]: case ex: ChannelClosedException => } b.result() - } - - //object Read: - // inline def unapply(): Option[A] = - // Some(read) + } def aOptRead: F[Option[A]] = asyncMonad.adoptCallbackStyle( f => @@ -56,6 +67,38 @@ trait ReadChannel[F[_], A]: inline def optRead: Option[A] = await(aOptRead)(using rAsyncMonad) + def foreach_async(f: A=>F[Unit]): F[Unit] = + given CpsAsyncMonad[F] = asyncMonad + async[F]{ + var done = false + while(!done) { + optRead match + case Some(v) => await(f(v)) + case None => done = true + } + } + + /** + * run code each time when new object is arriced. + * until end of stream is not reached + **/ + inline def foreach(f: A=>Unit): Unit = + await(foreach_async( x => rAsyncMonad.pure(f(x)) ))(using rAsyncMonad) + + + class DoneReadChannel extends ReadChannel[F,Unit]: + + def addReader(reader: Reader[Unit]): Unit = + thisReadChannel.addDoneReader(reader) + + def addDoneReader(reader: Reader[Unit]): Unit = + thisReadChannel.addDoneReader(reader) + + protected def asyncMonad: CpsAsyncMonad[F] = thisReadChannel.asyncMonad + + end DoneReadChannel + + class SimpleReader(f: Try[A] => Unit) extends Reader[A]: def canExpire: Boolean = false @@ -66,5 +109,9 @@ trait ReadChannel[F[_], A]: def markUsed(): Unit = () def markFree(): Unit = () + end SimpleReader + +end ReadChannel + diff --git a/shared/src/test/scala/gopher/channels/ChannelCloseSuite.scala b/shared/src/test/scala/gopher/channels/ChannelCloseSuite.scala index 06e894c2..62937db7 100644 --- a/shared/src/test/scala/gopher/channels/ChannelCloseSuite.scala +++ b/shared/src/test/scala/gopher/channels/ChannelCloseSuite.scala @@ -51,65 +51,99 @@ class ChannelCloseSuite extends FunSuite checkThrowWhenWriteClose("buffered", makeChannel[Int](100)) checkThrowWhenWriteClose("unbuffered", makeChannel[Int]()) - - /* - test("in async we must see throw") { - + checkThrowWhenWriteClose("promise", makeOnceChannel[Int]()) + + + test("after close we can read but not more, than was send (buffered)") { val channel = makeChannel[Int](100) - channel.close() - @volatile var catched = false - @volatile var notCatched = false - val p = async { - channel.write(1) - notCatched=true - } - p.recover{ - case ex: ChannelClosedException => catched = true - }.map(_ => assert(!notCatched && catched)) - - } - */ - - /* - test("after close we can read but not more, than was send") { - val channel = gopherApi.makeChannel[Int](100) @volatile var q1, q2 = 0 val p = async { channel <~ 1 - channel.close + channel.close() q1 = channel.read } val afterClose = p flatMap { _ => async{ val a = channel.read q2 = 2 } } - recoverToSucceededIf[ChannelClosedException] { - afterClose - } map (_ => assert(q1 == 1 && q2 != 2 )) + + afterClose.transform{ + case Failure(ex) => + assert(ex.isInstanceOf[ChannelClosedException]) + Success(()) + case Success(v) => + assert("Ok" == "ChannelClosedException") + Success(v) + } map (_ => { + assert(q1 == 1 && q2 != 2 ) + }) + } - test("close signal must be send") { - val channel = gopherApi.makeChannel[Int](100) - channel.close - @volatile var q = 0 - val fp = async { - val done = channel.done.read - q = 1 + test("after close we can read but not more, than was send (unbuffered)") { + val channel = makeChannel[Int]() + @volatile var q1, q2, q3 = 0 + val p = async { + channel <~ 1 + channel.close() + q1 = channel.read // will be unblocked after close and tbrwo exception + } + val consumer = async{ + q3 = channel.read // will be run + q2 = 2 + } + + val afterClose = p.flatMap(_ => consumer) + + p.transform{ + case Failure(ex) => + assert(ex.isInstanceOf[ChannelClosedException]) + Success(()) + case Success(v) => + assert("Ok" == "ChannelClosedException") + Success(v) + } map (_ => { + assert(q1 == 0) + assert(q2 == 2) + assert(q3 == 1) + }) + + } + + def checkCloseSignal(name: String, channel: gopher.Channel[Future,Int,Int])(implicit loc:munit.Location)= { + test(s"close signal must be send ($name)") { + channel.close() + @volatile var q = 0 + val fp = async { + val done = channel.done.read + q = 1 + } + fp map (_ => assert(q == 1)) } - fp map (_ => assert(q == 1)) } + checkCloseSignal("buffered", makeChannel[Int](100)) + checkCloseSignal("unbuffered", makeChannel[Int](100)) + + test("awrite to close must produce ChannelClosedFailure in Future") { - val channel = gopherApi.makeChannel[Int](100) + val channel = makeChannel[Int](100) channel.close - recoverToSucceededIf[ChannelClosedException]( - channel.awrite(1) - ) - } + var x = 1 + val f0 = async { + try { + channel.write(1) + assert("" == "Here should be unreachange") + }catch{ + case ex: ChannelClosedException => + // all ok + } + } + } + + -*/ -// def gopherApi = CommonTestObjects.gopherApi } From 80566bca61974cfdc198161dd3b33370c97d61d0 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Thu, 17 Dec 2020 23:14:29 +0200 Subject: [PATCH 020/161] added select for read.done --- shared/src/main/scala/gopher/Select.scala | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/shared/src/main/scala/gopher/Select.scala b/shared/src/main/scala/gopher/Select.scala index ba23ed78..d3e386e0 100644 --- a/shared/src/main/scala/gopher/Select.scala +++ b/shared/src/main/scala/gopher/Select.scala @@ -38,6 +38,10 @@ object Select: def appended(base: Expr[SelectGroup[F,S]])(using Quotes): Expr[SelectGroup[F,S]] = '{ $base.onTimeout($t)($f) } + case class DoneExression[F[_]:Type, A:Type, S:Type](ch: Expr[ReadChannel[F,A]], f: Expr[Unit=>S]) extends SelectorCaseExpr[F,S]: + def appended(base: Expr[SelectGroup[F,S]])(using Quotes): Expr[SelectGroup[F,S]] = + '{ $base.onRead($ch.done)($f) } + def onceImpl[F[_]:Type, A:Type](pf: Expr[PartialFunction[Any,A]], m: Expr[CpsSchedulingMonad[F]], api: Expr[Gopher[F]])(using Quotes): Expr[A] = @@ -98,6 +102,18 @@ object Select: TimeoutExpression(e.asExprOf[FiniteDuration], timeoutFun.asExprOf[FiniteDuration => S]) else reportError(s"Expected Time, we have ${ch.show}", ch.asExpr) + case b@Bind(v, tp@Typed(expr, TypeSelect(ch,"done"))) => + val readFun = makeLambda(v,tp.tpe,b.symbol,caseDef.rhs) + tp.tpe.asType match + case '[a] => + if (ch.tpe <:< TypeRepr.of[ReadChannel[F,a]]) then + DoneExression(ch.asExprOf[ReadChannel[F,a]],readFun.asExprOf[Unit=>S]) + else + reportError("done base is not a read channel", ch.asExpr) + case _ => + reportError("can't determinate read type", caseDef.pattern.asExpr) + + case _ => report.error( s""" From fab273a9ed7e04348ecb0330a291ee27f6ad2907 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Sat, 19 Dec 2020 09:18:34 +0200 Subject: [PATCH 021/161] porting DuppedChannel to 2.0.0 --- .../scala/gopher/channels/DuppedInput.scala | 41 ------------------- .../main/scala/gopher/impl/BaseChannel.scala | 11 +++-- .../scala/gopher/impl/BufferedChannel.scala | 14 +++++-- .../scala/gopher/impl/PromiseChannel.scala | 11 ++++- .../scala/gopher/impl/UnbufferedChannel.scala | 3 ++ .../gopher/impl/GuardedSPSCBaseChannel.scala | 10 +++-- .../impl/GuardedSPSCBufferedChannel.scala | 5 ++- .../scala/gopher/impl/PromiseChannel.scala | 14 +++---- shared/src/main/scala/gopher/Channel.scala | 4 +- .../src/main/scala/gopher/DuppedInput.scala | 31 ++++++++++++++ .../src/main/scala/gopher/ReadChannel.scala | 10 +++-- .../main/scala/gopher/impl/Expirable.scala | 8 ++++ .../gopher/channels/ChannelCloseSuite.scala | 3 -- .../gopher/channels/DuppedChannelsSuite.scala | 24 +++++++---- .../channels/history/AsyncSelectSuite.scala | 2 +- 15 files changed, 111 insertions(+), 80 deletions(-) delete mode 100644 0.99.x/src/main/scala/gopher/channels/DuppedInput.scala create mode 100644 shared/src/main/scala/gopher/DuppedInput.scala rename {0.99.x => shared}/src/test/scala/gopher/channels/DuppedChannelsSuite.scala (76%) diff --git a/0.99.x/src/main/scala/gopher/channels/DuppedInput.scala b/0.99.x/src/main/scala/gopher/channels/DuppedInput.scala deleted file mode 100644 index de7a2f97..00000000 --- a/0.99.x/src/main/scala/gopher/channels/DuppedInput.scala +++ /dev/null @@ -1,41 +0,0 @@ -package gopher.channels - -import gopher._ -import scala.annotation._ -import scala.concurrent._ -import scala.util._ -import java.util.concurrent.ConcurrentLinkedQueue -import java.util.concurrent.atomic.AtomicInteger -import async.Async._ - - - -class DuppedInput[A](origin:Input[A]) -{ - - def pair = (sink1, sink2) - - val sink1 = api.makeChannel[A](1) - val sink2 = api.makeChannel[A](1) - - // can't use macroses, so unroll by hands. - private val selector = api.select.forever; - selector.readingWithFlowTerminationAsync(origin, - (ec:ExecutionContext, ft: FlowTermination[Unit], a: A) => { - val f1 = sink1.awrite(a) - val f2 = sink2.awrite(a) - implicit val iec = ec - f1.flatMap(_ => f2)map(_ => ()) - } ) - selector.go.failed.foreach{ - case ex: ChannelClosedException => - sink1.close() - sink2.close() - } - - def api = origin.api - private implicit def ec:ExecutionContext = api.gopherExecutionContext - - - -} diff --git a/js/src/main/scala/gopher/impl/BaseChannel.scala b/js/src/main/scala/gopher/impl/BaseChannel.scala index ddddd189..e07e4ddb 100644 --- a/js/src/main/scala/gopher/impl/BaseChannel.scala +++ b/js/src/main/scala/gopher/impl/BaseChannel.scala @@ -8,16 +8,13 @@ import scala.util._ import scala.util.control.NonFatal -abstract class BaseChannel[F[_]:CpsAsyncMonad,A](root: JSGopher[F]) extends Channel[F,A,A]: +abstract class BaseChannel[F[_],A](override val gopherApi: JSGopher[F]) extends Channel[F,A,A]: protected val readers: Queue[Reader[A]] = Queue.empty protected val writers: Queue[Writer[A]] = Queue.empty protected val doneReaders: Queue[Reader[Unit]] = Queue.empty protected var closed: Boolean = false - protected override def asyncMonad: cps.CpsAsyncMonad[F] = summon[CpsAsyncMonad[F]] - - override def close(): Unit = closed = true processClose() @@ -37,6 +34,7 @@ abstract class BaseChannel[F[_]:CpsAsyncMonad,A](root: JSGopher[F]) extends Chan def addReader(reader: Reader[A]): Unit = if (closed && isEmpty ) { reader.capture().foreach{ f => + reader.markUsed() submitTask( () => f(Failure(new ChannelClosedException())) ) @@ -49,6 +47,7 @@ abstract class BaseChannel[F[_]:CpsAsyncMonad,A](root: JSGopher[F]) extends Chan def addWriter(writer: Writer[A]): Unit = if (closed) { writer.capture().foreach{ (a,f) => + writer.markUsed() submitTask( () => f(Failure(new ChannelClosedException())) ) @@ -61,6 +60,7 @@ abstract class BaseChannel[F[_]:CpsAsyncMonad,A](root: JSGopher[F]) extends Chan def addDoneReader(reader: Reader[Unit]): Unit = if (closed && isEmpty) { reader.capture().foreach{ f => + reader.markUsed() submitTask( () => f(Success(()))) } } else { @@ -78,6 +78,7 @@ abstract class BaseChannel[F[_]:CpsAsyncMonad,A](root: JSGopher[F]) extends Chan val success = Success(()) doneReaders.foreach( reader => reader.capture().foreach{ f => + reader.markUsed() f(success) } ) @@ -86,6 +87,7 @@ abstract class BaseChannel[F[_]:CpsAsyncMonad,A](root: JSGopher[F]) extends Chan val channelClosed = Failure(ChannelClosedException()) readers.foreach{ reader => reader.capture().foreach{ f => + reader.markUsed() f(channelClosed) } } @@ -94,6 +96,7 @@ abstract class BaseChannel[F[_]:CpsAsyncMonad,A](root: JSGopher[F]) extends Chan val channelClosed = Failure(ChannelClosedException()) writers.foreach{ writer => writer.capture().foreach{ (a,f) => + writer.markUsed() f(channelClosed) } } diff --git a/js/src/main/scala/gopher/impl/BufferedChannel.scala b/js/src/main/scala/gopher/impl/BufferedChannel.scala index fe2134a0..a3bf379a 100644 --- a/js/src/main/scala/gopher/impl/BufferedChannel.scala +++ b/js/src/main/scala/gopher/impl/BufferedChannel.scala @@ -61,6 +61,7 @@ class BufferedChannel[F[_]:CpsAsyncMonad, A](gopherApi: JSGopher[F], bufSize: In progress = true reader.capture().foreach{ f => internalDequeueFinish() + reader.markUsed() submitTask( () => f(Success(a)) ) } progress @@ -69,10 +70,17 @@ class BufferedChannel[F[_]:CpsAsyncMonad, A](gopherApi: JSGopher[F], bufSize: In var progress = false if (!writers.isEmpty && !isFull) then val writer = writers.dequeue() - writer.capture().foreach{ case (a,f) => + writer.capture() match + case Some((a,f)) => internalEnqueue(a) - f(Success(())) - } + writer.markUsed() + submitTask( () => f(Success(())) ) + progress = true + case None => + if (!writer.isExpired) then + // impossible, we have no parallel execution + println(s"Deadlock detected, this=${this}, writer=${writer}, writer.isExpired=${writer.isExpired}, writer.capture=${writer.capture()}") + throw DeadlockDetected() progress diff --git a/js/src/main/scala/gopher/impl/PromiseChannel.scala b/js/src/main/scala/gopher/impl/PromiseChannel.scala index 710a8d5f..e02cd648 100644 --- a/js/src/main/scala/gopher/impl/PromiseChannel.scala +++ b/js/src/main/scala/gopher/impl/PromiseChannel.scala @@ -21,6 +21,7 @@ class PromiseChannel[F[_]:CpsAsyncMonad, A](gopherApi: JSGopher[F]) extends Base if (!w.isExpired) then w.capture() match case Some((a,f)) => + w.markUsed() submitTask(()=>f(Success(()))) value = Some(a) closed = true @@ -38,11 +39,17 @@ class PromiseChannel[F[_]:CpsAsyncMonad, A](gopherApi: JSGopher[F]) extends Base r.capture() match case Some(f) => if (!readed) then + r.markUsed() submitTask(()=>f(Success(value.get))) readed = true else - processCloseDone() // to have .done call befor channel-closed. - submitTask(()=>f(Failure(ChannelClosedException()))) + r.markFree() + processCloseDone() // to have .done call befor channel-closed. + if (!r.isExpired) then + r.capture().foreach{ f => + r.markUsed() + submitTask(()=>f(Failure(ChannelClosedException()))) + } case None => if (!r.isExpired) throw new DeadlockDetected() diff --git a/js/src/main/scala/gopher/impl/UnbufferedChannel.scala b/js/src/main/scala/gopher/impl/UnbufferedChannel.scala index 81cb4d5f..9656aaec 100644 --- a/js/src/main/scala/gopher/impl/UnbufferedChannel.scala +++ b/js/src/main/scala/gopher/impl/UnbufferedChannel.scala @@ -32,9 +32,12 @@ class UnbufferedChannel[F[_]:CpsAsyncMonad, A](gopherApi: JSGopher[F]) extends B submitTask( () => writeFun(Success(())) ) progress = true done = true + writer.markUsed() + reader.markUsed() case None => // impossible, because in js we have-no interleavinf, bug anyway // let's fallback + reader.markFree() readers.prepend(reader) case None => // impossible, but let's fallback diff --git a/jvm/src/main/scala/gopher/impl/GuardedSPSCBaseChannel.scala b/jvm/src/main/scala/gopher/impl/GuardedSPSCBaseChannel.scala index 025ff939..63925610 100644 --- a/jvm/src/main/scala/gopher/impl/GuardedSPSCBaseChannel.scala +++ b/jvm/src/main/scala/gopher/impl/GuardedSPSCBaseChannel.scala @@ -20,7 +20,7 @@ import scala.util.Failure * Step functions is executed in some thread loop, and in the same time, only one instance of step function is running. * (which is ensured by guard) **/ -abstract class GuardedSPSCBaseChannel[F[_]:CpsAsyncMonad,A](gopherApi: JVMGopher[F], controlExecutor: ExecutorService, taskExecutor: ExecutorService) extends Channel[F,A,A]: +abstract class GuardedSPSCBaseChannel[F[_]:CpsAsyncMonad,A](override val gopherApi: JVMGopher[F], controlExecutor: ExecutorService, taskExecutor: ExecutorService) extends Channel[F,A,A]: import GuardedSPSCBaseChannel._ @@ -32,8 +32,6 @@ abstract class GuardedSPSCBaseChannel[F[_]:CpsAsyncMonad,A](gopherApi: JVMGopher protected val stepGuard = new AtomicInteger(STEP_FREE) - override protected def asyncMonad: CpsAsyncMonad[F] = summon[CpsAsyncMonad[F]] - protected val stepRunnable: Runnable = (()=>entryStep()) @@ -102,6 +100,7 @@ abstract class GuardedSPSCBaseChannel[F[_]:CpsAsyncMonad,A](gopherApi: JVMGopher case Some(f) => progress = true taskExecutor.execute(() => f(Failure(new ChannelClosedException())) ) + r.markUsed() case None => progress = true progressWaitReader(r) @@ -117,6 +116,7 @@ abstract class GuardedSPSCBaseChannel[F[_]:CpsAsyncMonad,A](gopherApi: JVMGopher case Some((a,f)) => progress = true taskExecutor.execute(() => f(Failure(new ChannelClosedException)) ) + w.markUsed() case None => progress = true progressWaitWriter(w) @@ -132,6 +132,7 @@ abstract class GuardedSPSCBaseChannel[F[_]:CpsAsyncMonad,A](gopherApi: JVMGopher case Some(f) => progress = true taskExecutor.execute(() => f(Success(()))) + r.markUsed() case None => progressWaitDoneReader(r) } @@ -145,9 +146,11 @@ abstract class GuardedSPSCBaseChannel[F[_]:CpsAsyncMonad,A](gopherApi: JVMGopher w.capture() match case Some((a,f)) => taskExecutor.execute(() => f(Failure(new ChannelClosedException)) ) + w.markUsed() done = true case None => if (!w.isExpired) then + w.markFree() Thread.onSpinWait() } @@ -166,6 +169,7 @@ abstract class GuardedSPSCBaseChannel[F[_]:CpsAsyncMonad,A](gopherApi: JVMGopher protected def progressWait[T <: Expirable[_]](v:T, queue: ConcurrentLinkedDeque[T]): Unit = if (!v.isExpired) + v.markFree() if (queue.isEmpty) Thread.onSpinWait() queue.addLast(v) diff --git a/jvm/src/main/scala/gopher/impl/GuardedSPSCBufferedChannel.scala b/jvm/src/main/scala/gopher/impl/GuardedSPSCBufferedChannel.scala index 4d605f21..af7ad566 100644 --- a/jvm/src/main/scala/gopher/impl/GuardedSPSCBufferedChannel.scala +++ b/jvm/src/main/scala/gopher/impl/GuardedSPSCBufferedChannel.scala @@ -120,6 +120,7 @@ taskExecutor: ExecutorService) extends GuardedSPSCBaseChannel[F,A](gopherApi,con done = true case None => if !reader.isExpired then + reader.markFree() nonExpiredBusyReads = nonExpiredBusyReads.enqueue(reader) } while(nonExpiredBusyReads.nonEmpty) { @@ -145,10 +146,12 @@ taskExecutor: ExecutorService) extends GuardedSPSCBaseChannel[F,A](gopherApi,con if (state.write(a)) then taskExecutor.execute(() => f(Success(()))) progress = true + writer.markUsed() else // impossible, because state //TODO: log - println("impossibe,unsuccesfull write after !isFull") + //log("impossibe,unsuccesfull write after !isFull") + writer.markFree() writers.addFirst(writer) case None => if (!writer.isExpired) diff --git a/jvm/src/main/scala/gopher/impl/PromiseChannel.scala b/jvm/src/main/scala/gopher/impl/PromiseChannel.scala index 2b55c345..25f02230 100644 --- a/jvm/src/main/scala/gopher/impl/PromiseChannel.scala +++ b/jvm/src/main/scala/gopher/impl/PromiseChannel.scala @@ -15,7 +15,7 @@ import scala.util.Failure /** * Channel is closed immediatly after successfull write. **/ - class PromiseChannel[F[_]:CpsAsyncMonad,A](gopherApi: JVMGopher[F], taskExecutor: ExecutorService) extends Channel[F,A,A]: + class PromiseChannel[F[_],A](override val gopherApi: JVMGopher[F], taskExecutor: ExecutorService) extends Channel[F,A,A]: protected val readers = new ConcurrentLinkedDeque[Reader[A]]() protected val doneReaders = new ConcurrentLinkedDeque[Reader[Unit]]() @@ -23,8 +23,6 @@ import scala.util.Failure protected val closed: AtomicBoolean = new AtomicBoolean(false) protected val readed: AtomicBoolean = new AtomicBoolean(false) - protected override def asyncMonad = summon[CpsAsyncMonad[F]] - def addReader(reader: Reader[A]): Unit = if (ref.get() eq null) then readers.add(reader) @@ -34,11 +32,11 @@ import scala.util.Failure while(!done && !reader.isExpired) { reader.capture() match case Some(f) => + reader.markUsed() taskExecutor.execute(() => f(Failure(new ChannelClosedException()))) done = true case None => if (!reader.isExpired) then - reader.markFree() Thread.onSpinWait() } @@ -50,16 +48,17 @@ import scala.util.Failure val ar: AnyRef = a.asInstanceOf[AnyRef] // if (ref.compareAndSet(null,ar) && !closed.get() ) then closed.lazySet(true) + writer.markFree() step() done = true else + writer.markUsed() taskExecutor.execute(() => f(Failure(new ChannelClosedException()))) done = true case None => - if (!writer.isExpired) { - writer.markFree() + if (!writer.isExpired) Thread.onSpinWait() - } + def addDoneReader(reader: Reader[Unit]): Unit = if (!closed.get()) then @@ -69,6 +68,7 @@ import scala.util.Failure while(!done & !reader.isExpired) { reader.capture() match case Some(f) => + reader.markUsed() taskExecutor.execute(()=>f(Success(()))) done = true case None => diff --git a/shared/src/main/scala/gopher/Channel.scala b/shared/src/main/scala/gopher/Channel.scala index 731d218f..9f809dd8 100644 --- a/shared/src/main/scala/gopher/Channel.scala +++ b/shared/src/main/scala/gopher/Channel.scala @@ -5,9 +5,7 @@ import java.io.Closeable trait Channel[F[_],W,R] extends WriteChannel[F,W] with ReadChannel[F,R] with Closeable: - override protected def asyncMonad: CpsAsyncMonad[F] - - //protected override def asyncMonad: CpsAsyncMonad[F] + override protected def gopherApi: Gopher[F] end Channel diff --git a/shared/src/main/scala/gopher/DuppedInput.scala b/shared/src/main/scala/gopher/DuppedInput.scala new file mode 100644 index 00000000..1324cf7f --- /dev/null +++ b/shared/src/main/scala/gopher/DuppedInput.scala @@ -0,0 +1,31 @@ +package gopher + +import cps._ +import scala.annotation._ +import scala.concurrent._ +import scala.util._ +import java.util.concurrent.ConcurrentLinkedQueue +import java.util.concurrent.atomic.AtomicInteger + + + + +class DuppedInput[F[_],A](origin:ReadChannel[F,A])(using api:Gopher[F]) +{ + + def pair = (sink1, sink2) + + val sink1 = makeChannel[A](1) + val sink2 = makeChannel[A](1) + + val runner = SelectLoop[F](api)(using api.asyncMonad).onRead(origin){a => + val f1 = sink1.awrite(a) + val f2 = sink2.awrite(a) + true + }.onRead(origin.done){ _ => + sink1.close() + sink2.close() + false + }.runAsync() + +} diff --git a/shared/src/main/scala/gopher/ReadChannel.scala b/shared/src/main/scala/gopher/ReadChannel.scala index 684e6fd3..4274b5d7 100644 --- a/shared/src/main/scala/gopher/ReadChannel.scala +++ b/shared/src/main/scala/gopher/ReadChannel.scala @@ -12,9 +12,11 @@ trait ReadChannel[F[_], A]: type read = A - // workarround for https://github.com/lampepfl/dotty/issues/10477 - protected def asyncMonad: CpsAsyncMonad[F] + protected def gopherApi: Gopher[F] + + protected def asyncMonad: CpsSchedulingMonad[F] = gopherApi.asyncMonad + // workarround for https://github.com/lampepfl/dotty/issues/10477 protected def rAsyncMonad: CpsAsyncMonad[F] = asyncMonad def addReader(reader: Reader[A]): Unit @@ -85,6 +87,8 @@ trait ReadChannel[F[_], A]: inline def foreach(f: A=>Unit): Unit = await(foreach_async( x => rAsyncMonad.pure(f(x)) ))(using rAsyncMonad) + def dup(): (ReadChannel[F,A], ReadChannel[F,A]) = + DuppedInput(this)(using gopherApi).pair class DoneReadChannel extends ReadChannel[F,Unit]: @@ -94,7 +98,7 @@ trait ReadChannel[F[_], A]: def addDoneReader(reader: Reader[Unit]): Unit = thisReadChannel.addDoneReader(reader) - protected def asyncMonad: CpsAsyncMonad[F] = thisReadChannel.asyncMonad + protected def gopherApi: Gopher[F] = thisReadChannel.gopherApi end DoneReadChannel diff --git a/shared/src/main/scala/gopher/impl/Expirable.scala b/shared/src/main/scala/gopher/impl/Expirable.scala index 4bad10c4..a9021524 100644 --- a/shared/src/main/scala/gopher/impl/Expirable.scala +++ b/shared/src/main/scala/gopher/impl/Expirable.scala @@ -2,6 +2,14 @@ package gopher.impl import cps._ +/** +* Object, which can be expired +* (usually - reader or writer in SelectGroup) +* Usage protocol is next: +* capture +* if A inside is used, call markUsed and use A +* if A inside is unused for some reason -- call markFree +**/ trait Expirable[A]: /** diff --git a/shared/src/test/scala/gopher/channels/ChannelCloseSuite.scala b/shared/src/test/scala/gopher/channels/ChannelCloseSuite.scala index 62937db7..dec898d8 100644 --- a/shared/src/test/scala/gopher/channels/ChannelCloseSuite.scala +++ b/shared/src/test/scala/gopher/channels/ChannelCloseSuite.scala @@ -142,9 +142,6 @@ class ChannelCloseSuite extends FunSuite } - - - } diff --git a/0.99.x/src/test/scala/gopher/channels/DuppedChannelsSuite.scala b/shared/src/test/scala/gopher/channels/DuppedChannelsSuite.scala similarity index 76% rename from 0.99.x/src/test/scala/gopher/channels/DuppedChannelsSuite.scala rename to shared/src/test/scala/gopher/channels/DuppedChannelsSuite.scala index dff23a78..6deabf33 100644 --- a/0.99.x/src/test/scala/gopher/channels/DuppedChannelsSuite.scala +++ b/shared/src/test/scala/gopher/channels/DuppedChannelsSuite.scala @@ -1,28 +1,36 @@ package gopher.channels +import cps._ +import cps.monads.FutureAsyncMonad import gopher._ -import org.scalatest._ +import munit._ import scala.concurrent._ import scala.concurrent.duration._ import scala.language._ import scala.util._ -class DuppedChannelsSuite extends AsyncFunSuite { +class DuppedChannelsSuite extends FunSuite { + + import scala.concurrent.ExecutionContext.Implicits.global + given Gopher[Future] = SharedGopherAPI.apply[Future]() test("duped input must show two") { - val ch = gopherApi.makeChannel[String]() - val dupped = ch.dup - ch.awrite("1") + val ch = makeChannel[String]() + val dupped = ch.dup() + val r0 = ch.awrite("1") val r1 = dupped._1.aread val r2 = dupped._2.aread val r = for(v1 <- r1; v2 <- r2) yield (v1,v2) - r map (x => assert(x === ("1","1")) ) + r map {x => + assert(x == ("1","1")) + } } +/* test("output is blocked by both inputs") { import CommonTestObjects.FutureWithTimeout val ch = gopherApi.makeChannel[Int]() @@ -54,12 +62,10 @@ class DuppedChannelsSuite extends AsyncFunSuite { } } +*/ - def gopherApi = CommonTestObjects.gopherApi - - } diff --git a/shared/src/test/scala/gopher/channels/history/AsyncSelectSuite.scala b/shared/src/test/scala/gopher/channels/history/AsyncSelectSuite.scala index 474eb07c..31a864ab 100644 --- a/shared/src/test/scala/gopher/channels/history/AsyncSelectSuite.scala +++ b/shared/src/test/scala/gopher/channels/history/AsyncSelectSuite.scala @@ -62,7 +62,7 @@ class AsyncSelectSuite extends FunSuite { } - test("async base: select write, select read") { + test("async base: select write, select read".only) { val channel = makeChannel[Int](10) From 9d3f0f8e57841e5954b21da148c41b4f97ea9da7 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Sat, 19 Dec 2020 11:05:57 +0200 Subject: [PATCH 022/161] scala 3.0.0-M3 --- build.sbt | 6 +++--- project/build.properties | 2 +- project/plugins.sbt | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/build.sbt b/build.sbt index f650225b..41db0073 100644 --- a/build.sbt +++ b/build.sbt @@ -1,6 +1,6 @@ //val dottyVersion = "3.0.0-M1-bin-20201022-b26dbc4-NIGHTLY" //val dottyVersion = "3.0.0-RC1-bin-SNAPSHOT" -val dottyVersion = "3.0.0-M2" +val dottyVersion = "3.0.0-M3" //val dottyVersion = dottyLatestNightlyBuild.get @@ -10,8 +10,8 @@ val sharedSettings = Seq( scalaVersion := dottyVersion, name := "scala-gopher", resolvers += "Local Ivy Repository" at "file://"+Path.userHome.absolutePath+"/.ivy2/local", - libraryDependencies += "com.github.rssh" %%% "dotty-cps-async" % "0.3.4p1-M2-SNAPSHOT", - libraryDependencies += "org.scalameta" %%% "munit" % "0.7.19" % Test, + libraryDependencies += "com.github.rssh" %%% "dotty-cps-async" % "0.3.5", + libraryDependencies += "org.scalameta" %%% "munit" % "0.7.20" % Test, testFrameworks += new TestFramework("munit.Framework") ) diff --git a/project/build.properties b/project/build.properties index 7de0a938..c06db1bb 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=1.4.4 +sbt.version=1.4.5 diff --git a/project/plugins.sbt b/project/plugins.sbt index dafc0603..02c9a3c1 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,7 +1,7 @@ -addSbtPlugin("ch.epfl.lamp" % "sbt-dotty" % "0.4.6") -addSbtPlugin("com.jsuereth" % "sbt-pgp" % "2.0.1") +addSbtPlugin("ch.epfl.lamp" % "sbt-dotty" % "0.5.1") +addSbtPlugin("com.jsuereth" % "sbt-pgp" % "2.0.2") addSbtPlugin("com.typesafe.sbt" % "sbt-site" % "1.4.0") addSbtPlugin("com.typesafe.sbt" % "sbt-ghpages" % "0.6.3") addSbtPlugin("org.portable-scala" % "sbt-scalajs-crossproject" % "1.0.0") -addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.3.0") +addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.3.1") From 8ba0362516f3a2fce0aa505bb3d3cc0444cf93f3 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Sat, 19 Dec 2020 11:16:09 +0200 Subject: [PATCH 023/161] changed deprecated API --- shared/src/main/scala/gopher/Select.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/shared/src/main/scala/gopher/Select.scala b/shared/src/main/scala/gopher/Select.scala index d3e386e0..5aec9eda 100644 --- a/shared/src/main/scala/gopher/Select.scala +++ b/shared/src/main/scala/gopher/Select.scala @@ -46,7 +46,7 @@ object Select: def onceImpl[F[_]:Type, A:Type](pf: Expr[PartialFunction[Any,A]], m: Expr[CpsSchedulingMonad[F]], api: Expr[Gopher[F]])(using Quotes): Expr[A] = import quotes.reflect._ - onceImplTree[F,A](Term.of(pf), m, api).asExprOf[A] + onceImplTree[F,A](pf.asTerm, m, api).asExprOf[A] def onceImplTree[F[_]:Type, S:Type](using Quotes)(pf: quotes.reflect.Term, m: Expr[CpsSchedulingMonad[F]], api: Expr[Gopher[F]]): quotes.reflect.Term = import quotes.reflect._ @@ -67,7 +67,7 @@ object Select: parseCaseDef(e).appended(s) } val r = '{ $g.run() } - Term.of(r) + r.asTerm def parseCaseDef[F[_]:Type,S:Type](using Quotes)(caseDef: quotes.reflect.CaseDef): SelectorCaseExpr[F,S] = From 0669cc1e5a1eb4f7be591cf430d407e2c4ae61cb Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Sun, 20 Dec 2020 11:13:15 +0200 Subject: [PATCH 024/161] implemented dupped channeks, fix exclusive condition in select group --- .../main/scala/gopher/impl/BaseChannel.scala | 90 +++++++++---------- .../scala/gopher/impl/BufferedChannel.scala | 1 - .../gopher/impl/GuardedSPSCBaseChannel.scala | 2 - .../impl/GuardedSPSCBufferedChannel.scala | 1 - .../scala/gopher/impl/PromiseChannel.scala | 59 ++++++------ .../src/main/scala/gopher/DuppedInput.scala | 10 ++- .../src/main/scala/gopher/SelectGroup.scala | 35 ++++++-- shared/src/main/scala/gopher/SelectLoop.scala | 50 ++++++----- shared/src/main/scala/gopher/Time.scala | 13 ++- .../gopher/channels/ChannelCloseSuite.scala | 1 + .../gopher/channels/DuppedChannelsSuite.scala | 35 ++++---- .../gopher/channels/SelectGroupTest.scala | 67 ++++++++++++++ 12 files changed, 233 insertions(+), 131 deletions(-) create mode 100644 shared/src/test/scala/gopher/channels/SelectGroupTest.scala diff --git a/js/src/main/scala/gopher/impl/BaseChannel.scala b/js/src/main/scala/gopher/impl/BaseChannel.scala index e07e4ddb..c86f6251 100644 --- a/js/src/main/scala/gopher/impl/BaseChannel.scala +++ b/js/src/main/scala/gopher/impl/BaseChannel.scala @@ -32,76 +32,66 @@ abstract class BaseChannel[F[_],A](override val gopherApi: JSGopher[F]) extends } def addReader(reader: Reader[A]): Unit = - if (closed && isEmpty ) { - reader.capture().foreach{ f => - reader.markUsed() - submitTask( () => - f(Failure(new ChannelClosedException())) - ) - } - } else { - readers.enqueue(reader) - process() - } + readers.enqueue(reader) + process() def addWriter(writer: Writer[A]): Unit = - if (closed) { - writer.capture().foreach{ (a,f) => - writer.markUsed() - submitTask( () => - f(Failure(new ChannelClosedException())) - ) - } - } else { - writers.enqueue(writer) - process() + if (closed) { + writer.capture().foreach{ (a,f) => + writer.markUsed() + submitTask( () => + f(Failure(new ChannelClosedException())) + ) } + } else { + writers.enqueue(writer) + process() + } def addDoneReader(reader: Reader[Unit]): Unit = - if (closed && isEmpty) { - reader.capture().foreach{ f => + if (closed && isEmpty) { + reader.capture() match + case Some(f) => reader.markUsed() submitTask( () => f(Success(()))) - } - } else { - doneReaders.enqueue(reader) - } + case None => + // mb is blocked and will be evaluated in + doneReaders.enqueue(reader) + process() + } else { + doneReaders.enqueue(reader) + } protected def processClose(): Unit = if (isEmpty) then processCloseDone() - submitTask(processCloseWriters) submitTask(processCloseReaders) - + submitTask(processCloseWriters) + + protected def exhauseQueue[T <: Expirable[A],A](queue: Queue[T], action: A => Unit): Unit = + while(!queue.isEmpty) { + val v = queue.dequeue() + if (!v.isExpired) then + v.capture() match + case Some(a) => + v.markUsed() + action(a) + case None => + throw DeadlockDetected() + } protected def processCloseDone(): Unit = val success = Success(()) - doneReaders.foreach( reader => - reader.capture().foreach{ f => - reader.markUsed() - f(success) - } - ) - + exhauseQueue(doneReaders, f => f(success)) + protected def processCloseReaders(): Unit = val channelClosed = Failure(ChannelClosedException()) - readers.foreach{ reader => - reader.capture().foreach{ f => - reader.markUsed() - f(channelClosed) - } - } + exhauseQueue(readers, f => f(channelClosed)) protected def processCloseWriters(): Unit = val channelClosed = Failure(ChannelClosedException()) - writers.foreach{ writer => - writer.capture().foreach{ (a,f) => - writer.markUsed() - f(channelClosed) - } - } - - + exhauseQueue(writers, { case (a,f) => f(channelClosed) }) + protected def isEmpty: Boolean protected def process(): Unit diff --git a/js/src/main/scala/gopher/impl/BufferedChannel.scala b/js/src/main/scala/gopher/impl/BufferedChannel.scala index a3bf379a..cf545ffb 100644 --- a/js/src/main/scala/gopher/impl/BufferedChannel.scala +++ b/js/src/main/scala/gopher/impl/BufferedChannel.scala @@ -79,7 +79,6 @@ class BufferedChannel[F[_]:CpsAsyncMonad, A](gopherApi: JSGopher[F], bufSize: In case None => if (!writer.isExpired) then // impossible, we have no parallel execution - println(s"Deadlock detected, this=${this}, writer=${writer}, writer.isExpired=${writer.isExpired}, writer.capture=${writer.capture()}") throw DeadlockDetected() progress diff --git a/jvm/src/main/scala/gopher/impl/GuardedSPSCBaseChannel.scala b/jvm/src/main/scala/gopher/impl/GuardedSPSCBaseChannel.scala index 63925610..52ea1def 100644 --- a/jvm/src/main/scala/gopher/impl/GuardedSPSCBaseChannel.scala +++ b/jvm/src/main/scala/gopher/impl/GuardedSPSCBaseChannel.scala @@ -150,7 +150,6 @@ abstract class GuardedSPSCBaseChannel[F[_]:CpsAsyncMonad,A](override val gopherA done = true case None => if (!w.isExpired) then - w.markFree() Thread.onSpinWait() } @@ -169,7 +168,6 @@ abstract class GuardedSPSCBaseChannel[F[_]:CpsAsyncMonad,A](override val gopherA protected def progressWait[T <: Expirable[_]](v:T, queue: ConcurrentLinkedDeque[T]): Unit = if (!v.isExpired) - v.markFree() if (queue.isEmpty) Thread.onSpinWait() queue.addLast(v) diff --git a/jvm/src/main/scala/gopher/impl/GuardedSPSCBufferedChannel.scala b/jvm/src/main/scala/gopher/impl/GuardedSPSCBufferedChannel.scala index af7ad566..69b39879 100644 --- a/jvm/src/main/scala/gopher/impl/GuardedSPSCBufferedChannel.scala +++ b/jvm/src/main/scala/gopher/impl/GuardedSPSCBufferedChannel.scala @@ -120,7 +120,6 @@ taskExecutor: ExecutorService) extends GuardedSPSCBaseChannel[F,A](gopherApi,con done = true case None => if !reader.isExpired then - reader.markFree() nonExpiredBusyReads = nonExpiredBusyReads.enqueue(reader) } while(nonExpiredBusyReads.nonEmpty) { diff --git a/jvm/src/main/scala/gopher/impl/PromiseChannel.scala b/jvm/src/main/scala/gopher/impl/PromiseChannel.scala index 25f02230..ef4bf9ea 100644 --- a/jvm/src/main/scala/gopher/impl/PromiseChannel.scala +++ b/jvm/src/main/scala/gopher/impl/PromiseChannel.scala @@ -29,16 +29,14 @@ import scala.util.Failure step() else var done = false - while(!done && !reader.isExpired) { - reader.capture() match - case Some(f) => - reader.markUsed() - taskExecutor.execute(() => f(Failure(new ChannelClosedException()))) - done = true - case None => - if (!reader.isExpired) then - Thread.onSpinWait() - } + reader.capture() match + case Some(f) => + reader.markUsed() + taskExecutor.execute(() => f(Failure(new ChannelClosedException()))) + done = true + case None => + readers.add(reader) + step() def addWriter(writer: Writer[A]): Unit = var done = false @@ -48,15 +46,15 @@ import scala.util.Failure val ar: AnyRef = a.asInstanceOf[AnyRef] // if (ref.compareAndSet(null,ar) && !closed.get() ) then closed.lazySet(true) - writer.markFree() + taskExecutor.execute(()=> f(Success(()))) + writer.markUsed() step() - done = true else - writer.markUsed() taskExecutor.execute(() => f(Failure(new ChannelClosedException()))) - done = true + writer.markUsed() + done = true case None => - if (!writer.isExpired) + if (!writer.isExpired) then Thread.onSpinWait() @@ -96,6 +94,7 @@ import scala.util.Failure r.capture() match case Some(f) => done = true + r.markUsed() if (readed.compareAndSet(false,true)) then val a = ar.nn.asInstanceOf[A] taskExecutor.execute(() => f(Success(a))) @@ -112,24 +111,13 @@ import scala.util.Failure else if (closed.get()) then closeAll() - def closeAll(): Unit = - while(!readers.isEmpty) { - val r = readers.poll() - if (!(r eq null) && !r.isExpired) then - r.capture() match - case Some(f) => - taskExecutor.execute(() => f(Failure(new ChannelClosedException))) - case None => - if (!r.isExpired) then - if (readers.isEmpty) then - Thread.onSpinWait() - readers.addLast(r) - } + def closeAll(): Unit = while(!doneReaders.isEmpty) { val r = doneReaders.poll() if !((r eq null) || r.isExpired) then r.capture() match case Some(f) => + r.markUsed() taskExecutor.execute(()=>f(Success(()))) case None => if (!r.isExpired) then @@ -137,5 +125,20 @@ import scala.util.Failure Thread.onSpinWait() doneReaders.addLast(r) } + while(!readers.isEmpty) { + val r = readers.poll() + if (!(r eq null) && !r.isExpired) then + r.capture() match + case Some(f) => + r.markUsed() + taskExecutor.execute(() => f(Failure(new ChannelClosedException))) + case None => + if (!r.isExpired) then + if (readers.isEmpty) then + Thread.onSpinWait() + readers.addLast(r) + } + + diff --git a/shared/src/main/scala/gopher/DuppedInput.scala b/shared/src/main/scala/gopher/DuppedInput.scala index 1324cf7f..77c056c3 100644 --- a/shared/src/main/scala/gopher/DuppedInput.scala +++ b/shared/src/main/scala/gopher/DuppedInput.scala @@ -18,11 +18,13 @@ class DuppedInput[F[_],A](origin:ReadChannel[F,A])(using api:Gopher[F]) val sink1 = makeChannel[A](1) val sink2 = makeChannel[A](1) - val runner = SelectLoop[F](api)(using api.asyncMonad).onRead(origin){a => - val f1 = sink1.awrite(a) - val f2 = sink2.awrite(a) + given CpsSchedulingMonad[F] = api.asyncMonad + + val runner = SelectLoop[F](api).onReadAsync(origin){a => async{ + val f1 = sink1.write(a) + val f2 = sink2.write(a) true - }.onRead(origin.done){ _ => + }}.onRead(origin.done){ _ => sink1.close() sink2.close() false diff --git a/shared/src/main/scala/gopher/SelectGroup.scala b/shared/src/main/scala/gopher/SelectGroup.scala index a84ddfdb..0000c98a 100644 --- a/shared/src/main/scala/gopher/SelectGroup.scala +++ b/shared/src/main/scala/gopher/SelectGroup.scala @@ -17,6 +17,8 @@ import scala.language.postfixOps **/ class SelectGroup[F[_]:CpsSchedulingMonad, S](api: Gopher[F]): + thisSelectGroup => + /** * instance of select group created for call of select. **/ @@ -67,13 +69,18 @@ class SelectGroup[F[_]:CpsSchedulingMonad, S](api: Gopher[F]): }) this + // reading call will be tranformed to reader_async in async expressions - def onRead_async[A](ch: ReadChannel[F,A])(f: A => F[S] ): F[this.type] = + def onReadAsync[A](ch: ReadChannel[F,A])(f: A => F[S] ): this.type = addReader[A](ch,{ case Success(a) => m.tryImpure(f(a)) case Failure(ex) => m.error(ex) }) - m.pure(this) + this + + // reading call will be tranformed to reader_async in async expressions + def onRead_async[A](ch: ReadChannel[F,A])(f: A => F[S] ): F[this.type] = + m.pure(onReadAsync(ch)(f)) /** * FluentDSL for user SelectGroup without macroses. @@ -89,12 +96,16 @@ class SelectGroup[F[_]:CpsSchedulingMonad, S](api: Gopher[F]): }) this - def onWrite_async[A](ch: WriteChannel[F,A], a:A) (f: A => F[S] ): F[this.type] = + + def onWriteAsync[A](ch: WriteChannel[F,A], a:A) (f: A => F[S] ): this.type = addWriter[A](ch,a,{ case Success(()) => m.tryImpure(f(a)) case Failure(ex) => m.error(ex) }) - m.pure(this) + this + + def onWrite_async[A](ch: WriteChannel[F,A], a:A) (f: A => F[S] ): F[this.type] = + m.pure(onWriteAsync(ch,a)(f)) def onTimeout(t:FiniteDuration)(f: FiniteDuration => S): SelectGroup[F,S] = @@ -104,12 +115,16 @@ class SelectGroup[F[_]:CpsSchedulingMonad, S](api: Gopher[F]): }) this - def onTimeout_async(t:FiniteDuration)(f: FiniteDuration => F[S]): F[SelectGroup[F,S]] = + def onTimeoutAsync(t:FiniteDuration)(f: FiniteDuration => F[S]): SelectGroup[F,S] = setTimeout(t,{ - case Success(x) => m.tryImpure(f(x)) - case Failure(ex) => m.error(ex) + case Success(x) => m.tryImpure(f(x)) + case Failure(ex) => m.error(ex) }) - m.pure(this) + this + + + def onTimeout_async(t:FiniteDuration)(f: FiniteDuration => F[S]): F[SelectGroup[F,S]] = + m.pure(onTimeoutAsync(t)(f)) // @@ -117,7 +132,9 @@ class SelectGroup[F[_]:CpsSchedulingMonad, S](api: Gopher[F]): def canExpire: Boolean = true def isExpired: Boolean = waitState.get()==2 def markUsed(): Unit = waitState.lazySet(2) - def markFree(): Unit = waitState.set(0) + def markFree(): Unit = { + waitState.set(0) + } diff --git a/shared/src/main/scala/gopher/SelectLoop.scala b/shared/src/main/scala/gopher/SelectLoop.scala index f600a67a..663acd35 100644 --- a/shared/src/main/scala/gopher/SelectLoop.scala +++ b/shared/src/main/scala/gopher/SelectLoop.scala @@ -13,15 +13,14 @@ class SelectLoop[F[_]:CpsSchedulingMonad](api: Gopher[F]): } this + // TODO: think about special notation for builders + def onReadAsync[A](ch: ReadChannel[F,A])(f: A => F[Boolean]): this.type = + groupBuilder = groupBuilder.andThen( _.onReadAsync(ch)(f) ) + this + // TODO: think about special notation for builders def onRead_async[A](ch: ReadChannel[F,A])(f: A => F[Boolean]): F[this.type] = - groupBuilder = groupBuilder.andThen{ - g => { - g.onRead_async(ch)(f) - g - } - } - summon[CpsMonad[F]].pure(this) + summon[CpsMonad[F]].pure(onReadAsync(ch)(f)) def onWrite[A](ch: WriteChannel[F,A], a: =>A)(f: A=>Boolean): this.type = @@ -30,13 +29,15 @@ class SelectLoop[F[_]:CpsSchedulingMonad](api: Gopher[F]): } this - def onWrite_async[A](ch: WriteChannel[F,A], a: =>A)(f: A=>F[Boolean]): F[this.type] = + def onWriteAsync[A](ch: WriteChannel[F,A], a: =>A)(f: A=>F[Boolean]): this.type = groupBuilder = groupBuilder.andThen{ - g => - g.onWrite_async(ch,a)(f) - g + g => g.onWriteAsync(ch,a)(f) } - summon[CpsMonad[F]].pure(this) + this + + + def onWrite_async[A](ch: WriteChannel[F,A], a: =>A)(f: A=>F[Boolean]): F[this.type] = + summon[CpsMonad[F]].pure(onWriteAsync(ch,a)(f)) def onTimeout(t: FiniteDuration)(f: FiniteDuration => Boolean): this.type = @@ -45,18 +46,27 @@ class SelectLoop[F[_]:CpsSchedulingMonad](api: Gopher[F]): } this - def onTimeout_async(t: FiniteDuration)(f: FiniteDuration => F[Boolean]): F[this.type] = + def onTimeoutAsync(t: FiniteDuration)(f: FiniteDuration => F[Boolean]): this.type = groupBuilder = groupBuilder.andThen{ - g => g.onTimeout_async(t)(f) - g + g => g.onTimeoutAsync(t)(f) } - summon[CpsMonad[F]].pure(this) + this + + + def onTimeout_async(t: FiniteDuration)(f: FiniteDuration => F[Boolean]): F[this.type] = + summon[CpsMonad[F]].pure(onTimeoutAsync(t)(f)) + def runAsync(): F[Unit] = async[F] { - while{ - val group = api.select.group[Boolean] - groupBuilder(group).run() - } do () + try + while{ + val group = api.select.group[Boolean] + val r = groupBuilder(group).run() + r + } do () + catch + case ex:Throwable => + ex.printStackTrace() } inline def run(): Unit = await(runAsync()) diff --git a/shared/src/main/scala/gopher/Time.scala b/shared/src/main/scala/gopher/Time.scala index f078759a..f4603ee0 100644 --- a/shared/src/main/scala/gopher/Time.scala +++ b/shared/src/main/scala/gopher/Time.scala @@ -121,6 +121,14 @@ object Time: def after[F[_]](duration: FiniteDuration)(using Gopher[F]): ReadChannel[F,FiniteDuration] = summon[Gopher[F]].time.after(duration) + + def asleep[F[_]](duration: FiniteDuration)(using Gopher[F]): F[FiniteDuration] = + summon[Gopher[F]].time.asleep(duration) + + inline def sleep[F[_]](duration: FiniteDuration)(using Gopher[F]): FiniteDuration = + summon[Gopher[F]].time.sleep(duration) + + /** * Task, which can be cancelled. **/ @@ -131,4 +139,7 @@ object Time: def onDone( listener: Try[Boolean]=>Unit ): Unit } - \ No newline at end of file + + + + diff --git a/shared/src/test/scala/gopher/channels/ChannelCloseSuite.scala b/shared/src/test/scala/gopher/channels/ChannelCloseSuite.scala index dec898d8..0e8baf28 100644 --- a/shared/src/test/scala/gopher/channels/ChannelCloseSuite.scala +++ b/shared/src/test/scala/gopher/channels/ChannelCloseSuite.scala @@ -17,6 +17,7 @@ class ChannelCloseSuite extends FunSuite import scala.concurrent.ExecutionContext.Implicits.global given Gopher[Future] = SharedGopherAPI.apply[Future]() + test("writing after close is impossile") { val channel = makeChannel[Int](100) diff --git a/shared/src/test/scala/gopher/channels/DuppedChannelsSuite.scala b/shared/src/test/scala/gopher/channels/DuppedChannelsSuite.scala index 6deabf33..2aee1276 100644 --- a/shared/src/test/scala/gopher/channels/DuppedChannelsSuite.scala +++ b/shared/src/test/scala/gopher/channels/DuppedChannelsSuite.scala @@ -7,7 +7,7 @@ import munit._ import scala.concurrent._ import scala.concurrent.duration._ -import scala.language._ +import scala.language.postfixOps import scala.util._ class DuppedChannelsSuite extends FunSuite { @@ -30,39 +30,44 @@ class DuppedChannelsSuite extends FunSuite { } -/* + test("output is blocked by both inputs") { - import CommonTestObjects.FutureWithTimeout - val ch = gopherApi.makeChannel[Int]() + //import CommonTestObjects.FutureWithTimeout + val ch = makeChannel[Int]() val aw=ch.awriteAll(1 to 100) - val (in1, in2) = ch.dup + val (in1, in2) = ch.dup() val at1 = in1.atake(100) - val awt = aw.withTimeout(1 second) - val w = recoverToSucceededIf[TimeoutException](awt) - w.map(_ => assert(!aw.isCompleted && !at1.isCompleted)).flatMap { x => - in2.atake(100) map (_ => assert(aw.isCompleted)) + // TODO:make withTimeout as extension ? + //val awt = aw.withTimeout(1 second) + async { + assert(!aw.isCompleted && !at1.isCompleted) + val res = await(in2.atake(100)) + await(aw) } } + test("on closing of main stream dupped outputs also closed.") { - val ch = gopherApi.makeChannel[Int](1) - val (in1, in2) = ch.dup - val f1 = go { + val ch = makeChannel[Int](1) + val (in1, in2) = ch.dup() + val f1 = async{ ch.write(1) ch.close() } for{ fx <- f1 x <- in1.aread r <- in1.aread.transformWith { - case Success(u) => Future failed new IllegalStateException("Mist be closed") - case Failure(u) => Future successful (assert(x == 1)) + case Success(u) => + Future failed new IllegalStateException("Mist be closed") + case Failure(u) => + Future successful (assert(x == 1)) } } yield { r } } -*/ + diff --git a/shared/src/test/scala/gopher/channels/SelectGroupTest.scala b/shared/src/test/scala/gopher/channels/SelectGroupTest.scala new file mode 100644 index 00000000..42aaa381 --- /dev/null +++ b/shared/src/test/scala/gopher/channels/SelectGroupTest.scala @@ -0,0 +1,67 @@ +package gopher.channels + +import gopher._ +import cps._ +import cps.monads.FutureAsyncMonad +import scala.concurrent.Future +import scala.concurrent.duration._ +import scala.language.postfixOps +import java.util.concurrent.atomic._ + + + +import munit._ + +class SelectGroupTest extends FunSuite { + + import scala.concurrent.ExecutionContext.Implicits.global + given Gopher[Future] = SharedGopherAPI.apply[Future]() + + + def exclusiveProcess(name: String, ch1: Channel[Future,Int, Int], ch2: Channel[Future,Int, Int])(implicit loc:munit.Location) = { + test( s"select group should not run few async processes in parallel ($name)" ){ + + + + val group = new SelectGroup[Future,Unit](summon[Gopher[Future]]) + + val inW1 = new AtomicInteger(0) + val inW2 = new AtomicInteger(0) + val commonCounter = new AtomicInteger(0) + val myFirst = new AtomicBoolean(false) + + + group.onReadAsync(ch1){ (a) => + val x1 = inW1.incrementAndGet() + commonCounter.incrementAndGet() + Time.asleep(1 second).map{_ => + assert(inW2.get() == 0) + val x2 = inW1.incrementAndGet() + } + }.onReadAsync(ch2){ (a) => + val x1 = inW2.incrementAndGet() + commonCounter.incrementAndGet() + Time.asleep(1 second).map{ _ => + assert(inW1.get() == 0) + val x2 = inW2.incrementAndGet() + } + } + + ch1.awrite(1) + ch2.awrite(2) + + async{ + group.run() + assert(commonCounter.get()==1) + } + + } + } + + exclusiveProcess("unbuffered-unbuffered", makeChannel[Int](), makeChannel[Int]() ) + exclusiveProcess("buffered-buffered", makeChannel[Int](10), makeChannel[Int](10) ) + exclusiveProcess("promise-promise", makeOnceChannel[Int](), makeOnceChannel[Int]() ) + exclusiveProcess("buffered-unbuffered", makeChannel[Int](10), makeChannel[Int]() ) + + +} From 1c1588fa998b70f283d0c8e91cd2bd3b50c073b6 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Wed, 23 Dec 2020 06:57:35 +0200 Subject: [PATCH 025/161] porting SelectSuite --- build.sbt | 2 +- .../src/main/scala/gopher/DuppedInput.scala | 7 +- .../src/main/scala/gopher/ReadChannel.scala | 5 +- shared/src/main/scala/gopher/SelectLoop.scala | 11 ++- .../scala/gopher/channels/SelectSuite.scala | 88 +++++++++---------- 5 files changed, 56 insertions(+), 57 deletions(-) rename {0.99.x => shared}/src/test/scala/gopher/channels/SelectSuite.scala (78%) diff --git a/build.sbt b/build.sbt index 41db0073..76bb52e3 100644 --- a/build.sbt +++ b/build.sbt @@ -10,7 +10,7 @@ val sharedSettings = Seq( scalaVersion := dottyVersion, name := "scala-gopher", resolvers += "Local Ivy Repository" at "file://"+Path.userHome.absolutePath+"/.ivy2/local", - libraryDependencies += "com.github.rssh" %%% "dotty-cps-async" % "0.3.5", + libraryDependencies += "com.github.rssh" %%% "dotty-cps-async" % "0.3.6-SNAPSHOT", libraryDependencies += "org.scalameta" %%% "munit" % "0.7.20" % Test, testFrameworks += new TestFramework("munit.Framework") ) diff --git a/shared/src/main/scala/gopher/DuppedInput.scala b/shared/src/main/scala/gopher/DuppedInput.scala index 77c056c3..6380adae 100644 --- a/shared/src/main/scala/gopher/DuppedInput.scala +++ b/shared/src/main/scala/gopher/DuppedInput.scala @@ -3,6 +3,7 @@ package gopher import cps._ import scala.annotation._ import scala.concurrent._ +import scala.concurrent.duration._ import scala.util._ import java.util.concurrent.ConcurrentLinkedQueue import java.util.concurrent.atomic.AtomicInteger @@ -10,13 +11,13 @@ import java.util.concurrent.atomic.AtomicInteger -class DuppedInput[F[_],A](origin:ReadChannel[F,A])(using api:Gopher[F]) +class DuppedInput[F[_],A](origin:ReadChannel[F,A], bufSize: Int=1, expiration: Duration = Duration.Inf)(using api:Gopher[F]) { def pair = (sink1, sink2) - val sink1 = makeChannel[A](1) - val sink2 = makeChannel[A](1) + val sink1 = makeChannel[A](bufSize,false,expiration) + val sink2 = makeChannel[A](bufSize,false,expiration) given CpsSchedulingMonad[F] = api.asyncMonad diff --git a/shared/src/main/scala/gopher/ReadChannel.scala b/shared/src/main/scala/gopher/ReadChannel.scala index 4274b5d7..b786f4ce 100644 --- a/shared/src/main/scala/gopher/ReadChannel.scala +++ b/shared/src/main/scala/gopher/ReadChannel.scala @@ -5,6 +5,7 @@ import gopher.impl._ import scala.util.Try import scala.util.Success import scala.util.Failure +import scala.concurrent.duration.Duration trait ReadChannel[F[_], A]: @@ -87,8 +88,8 @@ trait ReadChannel[F[_], A]: inline def foreach(f: A=>Unit): Unit = await(foreach_async( x => rAsyncMonad.pure(f(x)) ))(using rAsyncMonad) - def dup(): (ReadChannel[F,A], ReadChannel[F,A]) = - DuppedInput(this)(using gopherApi).pair + def dup(bufSize: Int=1, expiration: Duration=Duration.Inf): (ReadChannel[F,A], ReadChannel[F,A]) = + DuppedInput(this, bufSize, expiration)(using gopherApi).pair class DoneReadChannel extends ReadChannel[F,Unit]: diff --git a/shared/src/main/scala/gopher/SelectLoop.scala b/shared/src/main/scala/gopher/SelectLoop.scala index 663acd35..1f7fabd8 100644 --- a/shared/src/main/scala/gopher/SelectLoop.scala +++ b/shared/src/main/scala/gopher/SelectLoop.scala @@ -22,6 +22,8 @@ class SelectLoop[F[_]:CpsSchedulingMonad](api: Gopher[F]): def onRead_async[A](ch: ReadChannel[F,A])(f: A => F[Boolean]): F[this.type] = summon[CpsMonad[F]].pure(onReadAsync(ch)(f)) + inline def reading[A](ch: ReadChannel[F,A])(f: A=>Boolean): this.type = + onRead(ch)(f) def onWrite[A](ch: WriteChannel[F,A], a: =>A)(f: A=>Boolean): this.type = groupBuilder = groupBuilder.andThen{ @@ -35,9 +37,11 @@ class SelectLoop[F[_]:CpsSchedulingMonad](api: Gopher[F]): } this - - def onWrite_async[A](ch: WriteChannel[F,A], a: =>A)(f: A=>F[Boolean]): F[this.type] = - summon[CpsMonad[F]].pure(onWriteAsync(ch,a)(f)) + def onWrite_async[A](ch: WriteChannel[F,A], fa: ()=>F[A])(f: A=>F[Boolean]): F[this.type] = + api.asyncMonad.map(fa())(a => onWriteAsync(ch,a)(f)) + + inline def writing[A](ch: WriteChannel[F,A], a: =>A)(f: A=>Boolean): this.type = + onWrite(ch,a)(f) def onTimeout(t: FiniteDuration)(f: FiniteDuration => Boolean): this.type = @@ -66,6 +70,7 @@ class SelectLoop[F[_]:CpsSchedulingMonad](api: Gopher[F]): } do () catch case ex:Throwable => + // TODO: log ex.printStackTrace() } diff --git a/0.99.x/src/test/scala/gopher/channels/SelectSuite.scala b/shared/src/test/scala/gopher/channels/SelectSuite.scala similarity index 78% rename from 0.99.x/src/test/scala/gopher/channels/SelectSuite.scala rename to shared/src/test/scala/gopher/channels/SelectSuite.scala index acf0f703..5978907b 100644 --- a/0.99.x/src/test/scala/gopher/channels/SelectSuite.scala +++ b/shared/src/test/scala/gopher/channels/SelectSuite.scala @@ -1,86 +1,78 @@ package gopher.channels -import org.scalatest._ +import munit._ import scala.language._ import scala.concurrent._ import scala.concurrent.duration._ + +import cps._ import gopher._ -import gopher.tags._ +import cps.monads.FutureAsyncMonad class SelectSuite extends FunSuite { + import scala.concurrent.ExecutionContext.Implicits.global + given Gopher[Future] = SharedGopherAPI.apply[Future]() + + test("basic select with reading syntax sugar") { - val channel = gopherApi.makeChannel[Int](100) + val channel = makeChannel[Int](100) - val producer = channel.awriteAll(1 to 1000) - - @volatile var sum = 0; - val consumer = gopherApi.select.forever.reading(channel){ i => - sum = sum+i - if (i==1000) { - implicitly[FlowTermination[Unit]].doExit(()) - } else { - } - }.go - - + val producer = channel.awriteAll(1 to 1000) - Await.ready(consumer, 10.second) - - val xsum = (1 to 1000).sum - assert(xsum == sum) - } + async { - test("basic select with 'apply' reading syntax sugar") { - - val channel = gopherApi.makeChannel[Int](100) - val producer = channel.awriteAll(1 to 1000) - - @volatile var sum = 0; - val consumer = gopherApi.select.forever.reading(channel) { i => + @volatile var sum = 0; + val consumer = select.loop.reading(channel){ i => sum = sum+i - if (i==1000) gopherApi.currentFlow.exit(()) - }.go - - Await.ready(consumer, 1000.second) - val xsum = (1 to 1000).sum - assert(xsum == sum) + i < 1000 + }.runAsync() + await(consumer) + val xsum = (1 to 1000).sum + assert(xsum == sum) + } } + test("basic select with async reading form oter stream in apply") { - val channel1 = gopherApi.makeChannel[Int](100) - val channel2 = gopherApi.makeChannel[Int](100) + async{ + val channel1 = makeChannel[Int](100) + val channel2 = makeChannel[Int](100) - val producer1 = channel1.awriteAll(1 to 1000) - val producer2_1 = channel2.awriteAll(1 to 10) + val producer1 = channel1.awriteAll(1 to 1000) + val producer2_1 = channel2.awriteAll(1 to 10) - @volatile var sum = 0; - val consumer = gopherApi.select.forever.reading(channel1) { i1 => + + @volatile var sum = 0; + // but when reading instead onRead + // TODO: submit bug to doty + val consumer = select.loop.onRead(channel1) { i1 => val i2 = channel2.read sum = sum+i1 + i2 - if (i1==1000) gopherApi.currentFlow.exit(()) - }.go + (i1 < 1000) + } .runAsync() - assert(consumer.isCompleted == false, "consumer must not be complete after reading first stream" ) - assert(producer1.isCompleted == false) + assert(consumer.isCompleted == false, "consumer must not be complete after reading first stream" ) + assert(producer1.isCompleted == false) - val producer2_2 = channel2.awriteAll(1 to 1000) + val producer2_2 = channel2.awriteAll(1 to 1000) - Await.ready(consumer, 1000.second) + await(consumer) - assert(consumer.isCompleted) + assert(consumer.isCompleted) + } } - + /* test("basic select write with apply") { @@ -276,6 +268,6 @@ class SelectSuite extends FunSuite assert(sum > 100000) } - def gopherApi = CommonTestObjects.gopherApi + */ } From 8d71a0c87dc0a6552634d2aa0960e2691307c0eb Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Thu, 31 Dec 2020 00:00:12 +0200 Subject: [PATCH 026/161] implemented async loop api --- build.sbt | 9 +- .../src/main/scala/gopher/ReadChannel.scala | 8 +- shared/src/main/scala/gopher/Select.scala | 35 +++-- shared/src/main/scala/gopher/SelectLoop.scala | 4 +- .../src/main/scala/gopher/WriteChannel.scala | 14 +- .../scala/gopher/channels/SelectSuite.scala | 143 ++++++++++-------- 6 files changed, 119 insertions(+), 94 deletions(-) diff --git a/build.sbt b/build.sbt index 76bb52e3..0398ab4d 100644 --- a/build.sbt +++ b/build.sbt @@ -1,6 +1,5 @@ -//val dottyVersion = "3.0.0-M1-bin-20201022-b26dbc4-NIGHTLY" -//val dottyVersion = "3.0.0-RC1-bin-SNAPSHOT" -val dottyVersion = "3.0.0-M3" +val dottyVersion = "3.0.0-RC1-bin-SNAPSHOT" +//val dottyVersion = "3.0.0-M3" //val dottyVersion = dottyLatestNightlyBuild.get @@ -11,7 +10,7 @@ val sharedSettings = Seq( name := "scala-gopher", resolvers += "Local Ivy Repository" at "file://"+Path.userHome.absolutePath+"/.ivy2/local", libraryDependencies += "com.github.rssh" %%% "dotty-cps-async" % "0.3.6-SNAPSHOT", - libraryDependencies += "org.scalameta" %%% "munit" % "0.7.20" % Test, + libraryDependencies += "org.scalameta" %%% "munit" % "0.7.21-SNAPSHOT" % Test, testFrameworks += new TestFramework("munit.Framework") ) @@ -31,7 +30,7 @@ lazy val gopher = crossProject(JSPlatform, JVMPlatform) .settings(sharedSettings) .disablePlugins(SitePlugin) .jvmSettings( - scalacOptions ++= Seq( "-unchecked" ), + scalacOptions ++= Seq( "-unchecked", "-Ycheck:macro" ), ).jsSettings( // TODO: switch to ModuleES ? scalaJSLinkerConfig ~= { _.withModuleKind(ModuleKind.CommonJSModule) }, diff --git a/shared/src/main/scala/gopher/ReadChannel.scala b/shared/src/main/scala/gopher/ReadChannel.scala index b786f4ce..cac33dfa 100644 --- a/shared/src/main/scala/gopher/ReadChannel.scala +++ b/shared/src/main/scala/gopher/ReadChannel.scala @@ -81,12 +81,18 @@ trait ReadChannel[F[_], A]: } } + def aforeach_async(f: A=>F[Unit]): F[F[Unit]] = + rAsyncMonad.pure(foreach_async(f)) + + def aforeach(f: A=> Unit): F[Unit] = + foreach_async( x => rAsyncMonad.pure(f(x))) + /** * run code each time when new object is arriced. * until end of stream is not reached **/ inline def foreach(f: A=>Unit): Unit = - await(foreach_async( x => rAsyncMonad.pure(f(x)) ))(using rAsyncMonad) + await(aforeach(f))(using rAsyncMonad) def dup(bufSize: Int=1, expiration: Duration=Duration.Inf): (ReadChannel[F,A], ReadChannel[F,A]) = DuppedInput(this, bufSize, expiration)(using gopherApi).pair diff --git a/shared/src/main/scala/gopher/Select.scala b/shared/src/main/scala/gopher/Select.scala index 5aec9eda..2ad54be7 100644 --- a/shared/src/main/scala/gopher/Select.scala +++ b/shared/src/main/scala/gopher/Select.scala @@ -42,32 +42,41 @@ object Select: def appended(base: Expr[SelectGroup[F,S]])(using Quotes): Expr[SelectGroup[F,S]] = '{ $base.onRead($ch.done)($f) } + def onceImpl[F[_]:Type, A:Type](pf: Expr[PartialFunction[Any,A]], m: Expr[CpsSchedulingMonad[F]], api: Expr[Gopher[F]])(using Quotes): Expr[A] = + def builder(caseDefs: List[SelectorCaseExpr[F,A]]):Expr[A] = { + val s0 = '{ + new SelectGroup[F,A]($api)(using $m) + } + val g: Expr[SelectGroup[F,A]] = caseDefs.foldLeft(s0){(s,e) => + e.appended(s) + } + val r = '{ $g.run() } + r.asExprOf[A] + } + runImpl( builder, pf) - def onceImpl[F[_]:Type, A:Type](pf: Expr[PartialFunction[Any,A]], m: Expr[CpsSchedulingMonad[F]], api: Expr[Gopher[F]])(using Quotes): Expr[A] = + def runImpl[F[_]:Type, A:Type,B :Type](builder: List[SelectorCaseExpr[F,A]]=>Expr[B], + pf: Expr[PartialFunction[Any,A]])(using Quotes): Expr[B] = import quotes.reflect._ - onceImplTree[F,A](pf.asTerm, m, api).asExprOf[A] + runImplTree[F,A,B](builder, pf.asTerm) - def onceImplTree[F[_]:Type, S:Type](using Quotes)(pf: quotes.reflect.Term, m: Expr[CpsSchedulingMonad[F]], api: Expr[Gopher[F]]): quotes.reflect.Term = + def runImplTree[F[_]:Type, A:Type, B:Type](using Quotes)( + builder: List[SelectorCaseExpr[F,A]] => Expr[B], + pf: quotes.reflect.Term + ): Expr[B] = import quotes.reflect._ pf match case Lambda(valDefs, body) => - onceImplTree[F,S](body, m, api) + runImplTree[F,A,B](builder, body) case Inlined(_,List(),body) => - onceImplTree[F,S](body, m, api) + runImplTree[F,A,B](builder, body) case Match(scrutinee,cases) => //val caseExprs = cases map(x => parseCaseDef[F,A](x)) //if (caseExprs.find(_.isInstanceOf[DefaultExpression[?]]).isDefined) { // report.error("default is not supported") //} - val s0 = '{ - new SelectGroup[F,S]($api)(using $m) - } - val g: Expr[SelectGroup[F,S]] = cases.foldLeft(s0){(s,e) => - parseCaseDef(e).appended(s) - } - val r = '{ $g.run() } - r.asTerm + builder(cases.map(parseCaseDef[F,A](_))) def parseCaseDef[F[_]:Type,S:Type](using Quotes)(caseDef: quotes.reflect.CaseDef): SelectorCaseExpr[F,S] = diff --git a/shared/src/main/scala/gopher/SelectLoop.scala b/shared/src/main/scala/gopher/SelectLoop.scala index 1f7fabd8..49c1e996 100644 --- a/shared/src/main/scala/gopher/SelectLoop.scala +++ b/shared/src/main/scala/gopher/SelectLoop.scala @@ -59,7 +59,7 @@ class SelectLoop[F[_]:CpsSchedulingMonad](api: Gopher[F]): def onTimeout_async(t: FiniteDuration)(f: FiniteDuration => F[Boolean]): F[this.type] = summon[CpsMonad[F]].pure(onTimeoutAsync(t)(f)) - + def runAsync(): F[Unit] = async[F] { try @@ -77,6 +77,6 @@ class SelectLoop[F[_]:CpsSchedulingMonad](api: Gopher[F]): inline def run(): Unit = await(runAsync()) - + diff --git a/shared/src/main/scala/gopher/WriteChannel.scala b/shared/src/main/scala/gopher/WriteChannel.scala index 320c41c7..da6d5d15 100644 --- a/shared/src/main/scala/gopher/WriteChannel.scala +++ b/shared/src/main/scala/gopher/WriteChannel.scala @@ -17,18 +17,20 @@ trait WriteChannel[F[_], A]: addWriter(SimpleWriter(a, f)) ) - object write: - inline def apply(a:A): Unit = await(awrite(a))(using asyncMonad) - inline def unapply(a:A): Some[A] = ??? + //object write: + // inline def apply(a:A): Unit = await(awrite(a))(using asyncMonad) + // inline def unapply(a:A): Some[A] = ??? + + inline def write(a:A): Unit = await(awrite(a))(using asyncMonad) @targetName("write1") inline def <~ (a:A): Unit = await(awrite(a))(using asyncMonad) //def Write(x:A):WritePattern = new WritePattern(x) - class WritePattern(x:A): - inline def unapply(y:Any): Option[A] = - Some(x) + //class WritePattern(x:A): + // inline def unapply(y:Any): Option[A] = + // Some(x) def addWriter(writer: Writer[A]): Unit diff --git a/shared/src/test/scala/gopher/channels/SelectSuite.scala b/shared/src/test/scala/gopher/channels/SelectSuite.scala index 5978907b..1f5587a3 100644 --- a/shared/src/test/scala/gopher/channels/SelectSuite.scala +++ b/shared/src/test/scala/gopher/channels/SelectSuite.scala @@ -2,7 +2,7 @@ package gopher.channels import munit._ -import scala.language._ +import scala.language.postfixOps import scala.concurrent._ import scala.concurrent.duration._ @@ -72,101 +72,108 @@ class SelectSuite extends FunSuite } - /* + test("basic select write with apply") { - val channel = gopherApi.makeChannel[Int](1) + val channel = makeChannel[Int](1) - @volatile var x = 1 - @volatile var y = 1 - val producer = gopherApi.select.forever.writing(channel,x) { _ => + async { + @volatile var x = 1 + @volatile var y = 1 + val producer = select.loop.writing(channel,x) { _ => var z = x + y x=y y=z if (z > 1000) { channel.close() - gopherApi.currentFlow.exit(()) + false + } else { + true } - }.go + }.runAsync() - @volatile var last = 0 - channel.aforeach{ i=> + @volatile var last = 0 + channel.foreach{ i=> //System.out.printn(i) last=i - } + } - Await.ready(producer, 1000.second) - - assert(producer.isCompleted) - //assert(consumer.isCompleted) - assert(last!=0) + assert(last!=0) + await(producer) // should be completed shortly + + } } - test("basic select idlle with apply") { + + test("basic select timeout (was idle in 0.99) with apply") { @volatile var x = 0 - val selector = gopherApi.select.forever.idle{ - if (x >= 10) { - gopherApi.currentFlow.exit(()) - } else { - x=x+1 - } - }.go - - - Await.ready(selector, 10.second) - assert(selector.isCompleted) - assert(x==10) - + val selector = select.loop.onTimeout(5 millisecond){ dt => + x = x + 1 + x < 10 + }.runAsync() + + async { + await(selector) + assert(x == 10) + } + } - + + test("basic compound select with apply") { - import scala.concurrent.ExecutionContext.Implicits.global + async { + val channel1 = makeChannel[Int](1) + val channel2 = makeChannel[Int](1) + val channel3 = makeChannel[Int](1) + val channel4 = makeChannel[Int](1) - val channel1 = gopherApi.makeChannel[Int](1) - val channel2 = gopherApi.makeChannel[Int](1) - val channel3 = gopherApi.makeChannel[Int](1) - val channel4 = gopherApi.makeChannel[Int](1) + val producer = channel1.awriteAll(1 to 1000) - val producer = channel1.awriteAll(1 to 1000) + @volatile var x=0 + @volatile var nw=0 + @volatile var q = false + @volatile var ch1s=0 - @volatile var x=0 - @volatile var nw=0 - @volatile var q = false - @volatile var ch1s=0 - - val selector = gopherApi.select.forever.reading(channel1) { i => + val selector = select.loop.reading(channel1) { i => // read ch1 in selector channel4.awrite(i) - ch1s=i + ch1s=i + true }.reading(channel2) { i => - {}; // workarround for https://issues.scala-lang.org/browse/SI-8846 + //{}; // workarround for https://issues.scala-lang.org/browse/SI-8846 x=i //Console.println(s"reading from ch2, i=${i}") + true }.writing(channel3,x) { x => - {}; // workarround for https://issues.scala-lang.org/browse/SI-8846 - nw=nw+1 + //{}; // workarround for https://issues.scala-lang.org/browse/SI-8846 + nw=nw+1 //Console.println(s"writing ${x} to ch3, nw=${nw}") - }.idle { + true + }.onTimeout(5 milliseconds) { dt => //Console.println(s"idle, exiting") - {}; + //{}; + channel4.close() q=true - gopherApi.currentFlow.exit(()) - }.go + false + }.runAsync() - for(c <- channel4.async) channel2.write(c) - Await.ready(selector, 10.second) - assert(selector.isCompleted) - assert(q==true) + for(c <- channel4) + channel2.write(c) - } + await(selector) + assert(q==true) + + } + } - test("basic compound select with for syntax") { + /* + test("basic compound select with for syntax") { import scala.concurrent.ExecutionContext.Implicits.global import scala.async.Async._ @@ -187,27 +194,29 @@ class SelectSuite extends FunSuite //pending // for syntax will be next: - for(s <- gopherApi.select.forever) - s match { - case ir: channel1.read => + select.loop{ s => + select match { + case ir: channel1.read => channel4.awrite(ir) ch1s=ir - case iw: channel3.write if (iw==(x+1)) => + case iw: channel3.write if (iw==(x+1)) => {}; nw = nw+1 - case _ => {}; q=true - implicitly[FlowTermination[Unit]].doExit(()) + case t: time.after => q=true } } - for(c <- channel4.async) channel2.write(c) + async{ + for(c <- channel4) channel2.write(c) + } + await(selector) - Await.ready(selector, 10.second) - assert(selector.isCompleted) assert(q==true) - } + } + */ + /* test("basic select.once with reading syntax sugar") { From 2c8c2a6bd24745695c9ab561b37194f804d6057e Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Thu, 31 Dec 2020 01:30:04 +0200 Subject: [PATCH 027/161] implemented SelectLoop --- shared/src/main/scala/gopher/Select.scala | 31 +++++++++--- .../src/main/scala/gopher/SelectGroup.scala | 10 ++-- .../main/scala/gopher/SelectListeners.scala | 16 ++++++ shared/src/main/scala/gopher/SelectLoop.scala | 10 +++- .../scala/gopher/channels/SelectSuite.scala | 50 ++++++++++--------- 5 files changed, 80 insertions(+), 37 deletions(-) create mode 100644 shared/src/main/scala/gopher/SelectListeners.scala diff --git a/shared/src/main/scala/gopher/Select.scala b/shared/src/main/scala/gopher/Select.scala index 2ad54be7..6c530453 100644 --- a/shared/src/main/scala/gopher/Select.scala +++ b/shared/src/main/scala/gopher/Select.scala @@ -6,6 +6,7 @@ import scala.quoted._ import scala.compiletime._ import scala.concurrent.duration._ + class Select[F[_]:CpsSchedulingMonad](api: Gopher[F]): inline def apply[A](inline pf: PartialFunction[Any,A]): A = @@ -20,26 +21,26 @@ class Select[F[_]:CpsSchedulingMonad](api: Gopher[F]): object Select: sealed trait SelectGroupExpr[F[_],S]: - def toExpr: Expr[SelectGroup[F,S]] + def toExprOf[X <: SelectListeners[F,S]]: Expr[X] sealed trait SelectorCaseExpr[F[_]:Type, S:Type]: type Monad[X] = F[X] - def appended(base: Expr[SelectGroup[F,S]])(using Quotes): Expr[SelectGroup[F,S]] + def appended[L <: SelectListeners[F,S] : Type](base: Expr[L])(using Quotes): Expr[L] case class ReadExpression[F[_]:Type, A:Type, S:Type](ch: Expr[ReadChannel[F,A]], f: Expr[A => S]) extends SelectorCaseExpr[F,S]: - def appended(base: Expr[SelectGroup[F,S]])(using Quotes): Expr[SelectGroup[F,S]] = + def appended[L <: SelectListeners[F,S]: Type](base: Expr[L])(using Quotes): Expr[L] = '{ $base.onRead($ch)($f) } case class WriteExpression[F[_]:Type, A:Type, S:Type](ch: Expr[WriteChannel[F,A]], a: Expr[A], f: Expr[A => S]) extends SelectorCaseExpr[F,S]: - def appended(base: Expr[SelectGroup[F,S]])(using Quotes): Expr[SelectGroup[F,S]] = + def appended[L <: SelectListeners[F,S]: Type](base: Expr[L])(using Quotes): Expr[L] = '{ $base.onWrite($ch,$a)($f) } case class TimeoutExpression[F[_]:Type,S:Type](t: Expr[FiniteDuration], f: Expr[ FiniteDuration => S ]) extends SelectorCaseExpr[F,S]: - def appended(base: Expr[SelectGroup[F,S]])(using Quotes): Expr[SelectGroup[F,S]] = + def appended[L <: SelectListeners[F,S]: Type](base: Expr[L])(using Quotes): Expr[L] = '{ $base.onTimeout($t)($f) } case class DoneExression[F[_]:Type, A:Type, S:Type](ch: Expr[ReadChannel[F,A]], f: Expr[Unit=>S]) extends SelectorCaseExpr[F,S]: - def appended(base: Expr[SelectGroup[F,S]])(using Quotes): Expr[SelectGroup[F,S]] = + def appended[L <: SelectListeners[F,S]: Type](base: Expr[L])(using Quotes): Expr[L] = '{ $base.onRead($ch.done)($f) } def onceImpl[F[_]:Type, A:Type](pf: Expr[PartialFunction[Any,A]], m: Expr[CpsSchedulingMonad[F]], api: Expr[Gopher[F]])(using Quotes): Expr[A] = @@ -54,7 +55,21 @@ object Select: r.asExprOf[A] } runImpl( builder, pf) - + + def loopImpl[F[_]:Type](pf: Expr[PartialFunction[Any,Boolean]], m: Expr[CpsSchedulingMonad[F]], api: Expr[Gopher[F]])(using Quotes): Expr[Unit] = + def builder(caseDefs: List[SelectorCaseExpr[F,Boolean]]):Expr[Unit] = { + val s0 = '{ + new SelectLoop[F]($api)(using $m) + } + val g: Expr[SelectLoop[F]] = caseDefs.foldLeft(s0){(s,e) => + e.appended(s) + } + val r = '{ $g.run() } + r.asExprOf[Unit] + } + runImpl( builder, pf) + + def runImpl[F[_]:Type, A:Type,B :Type](builder: List[SelectorCaseExpr[F,A]]=>Expr[B], pf: Expr[PartialFunction[Any,A]])(using Quotes): Expr[B] = @@ -107,7 +122,7 @@ object Select: case b@Bind(v, tp@Typed(expr, TypeSelect(ch,"after"))) => val timeoutFun = makeLambda(v, tp.tpe, b.symbol, caseDef.rhs) val e = matchCaseDefCondition(caseDef, v) - if (ch.tpe =:= TypeRepr.of[gopher.Time]) + if (ch.tpe <:< TypeRepr.of[gopher.Time] || ch.tpe <:< TypeRepr.of[gopher.Time.type]) TimeoutExpression(e.asExprOf[FiniteDuration], timeoutFun.asExprOf[FiniteDuration => S]) else reportError(s"Expected Time, we have ${ch.show}", ch.asExpr) diff --git a/shared/src/main/scala/gopher/SelectGroup.scala b/shared/src/main/scala/gopher/SelectGroup.scala index 0000c98a..430c6315 100644 --- a/shared/src/main/scala/gopher/SelectGroup.scala +++ b/shared/src/main/scala/gopher/SelectGroup.scala @@ -15,7 +15,7 @@ import scala.language.postfixOps * Select group is a virtual 'lock' object, where only * ne fro rieader and writer can exists at the sae time. **/ -class SelectGroup[F[_]:CpsSchedulingMonad, S](api: Gopher[F]): +class SelectGroup[F[_]:CpsSchedulingMonad, S](api: Gopher[F]) extends SelectListeners[F,S]: thisSelectGroup => @@ -89,7 +89,7 @@ class SelectGroup[F[_]:CpsSchedulingMonad, S](api: Gopher[F]): * .onWrite(endSignal){ () => done=true } *``` **/ - def onWrite[A](ch: WriteChannel[F,A], a:A)(f: A =>S ): SelectGroup[F,S] = + def onWrite[A](ch: WriteChannel[F,A], a: =>A)(f: A =>S ): this.type = addWriter[A](ch,a,{ case Success(()) => m.tryPure(f(a)) case Failure(ex) => m.error(ex) @@ -108,14 +108,14 @@ class SelectGroup[F[_]:CpsSchedulingMonad, S](api: Gopher[F]): m.pure(onWriteAsync(ch,a)(f)) - def onTimeout(t:FiniteDuration)(f: FiniteDuration => S): SelectGroup[F,S] = + def onTimeout(t:FiniteDuration)(f: FiniteDuration => S): this.type = setTimeout(t,{ case Success(x) => m.tryPure(f(x)) case Failure(ex) => m.error(ex) }) this - def onTimeoutAsync(t:FiniteDuration)(f: FiniteDuration => F[S]): SelectGroup[F,S] = + def onTimeoutAsync(t:FiniteDuration)(f: FiniteDuration => F[S]): this.type = setTimeout(t,{ case Success(x) => m.tryImpure(f(x)) case Failure(ex) => m.error(ex) @@ -123,7 +123,7 @@ class SelectGroup[F[_]:CpsSchedulingMonad, S](api: Gopher[F]): this - def onTimeout_async(t:FiniteDuration)(f: FiniteDuration => F[S]): F[SelectGroup[F,S]] = + def onTimeout_async(t:FiniteDuration)(f: FiniteDuration => F[S]): F[this.type] = m.pure(onTimeoutAsync(t)(f)) diff --git a/shared/src/main/scala/gopher/SelectListeners.scala b/shared/src/main/scala/gopher/SelectListeners.scala new file mode 100644 index 00000000..6d5c2b0b --- /dev/null +++ b/shared/src/main/scala/gopher/SelectListeners.scala @@ -0,0 +1,16 @@ +package gopher + +import scala.concurrent.duration.FiniteDuration + +trait SelectListeners[F[_],S]: + + def onRead[A](ch: ReadChannel[F,A]) (f: A => S ): this.type + + def onWrite[A](ch: WriteChannel[F,A], a: =>A)(f: A => S): this.type + + def onTimeout(t: FiniteDuration)(f: FiniteDuration => S): this.type + + + + + diff --git a/shared/src/main/scala/gopher/SelectLoop.scala b/shared/src/main/scala/gopher/SelectLoop.scala index 49c1e996..4f90c791 100644 --- a/shared/src/main/scala/gopher/SelectLoop.scala +++ b/shared/src/main/scala/gopher/SelectLoop.scala @@ -1,9 +1,11 @@ package gopher import cps._ +import scala.quoted._ +import scala.compiletime._ import scala.concurrent.duration._ -class SelectLoop[F[_]:CpsSchedulingMonad](api: Gopher[F]): +class SelectLoop[F[_]:CpsSchedulingMonad](api: Gopher[F]) extends SelectListeners[F,Boolean]: private var groupBuilder: SelectGroup[F,Boolean] => SelectGroup[F,Boolean] = identity @@ -59,8 +61,14 @@ class SelectLoop[F[_]:CpsSchedulingMonad](api: Gopher[F]): def onTimeout_async(t: FiniteDuration)(f: FiniteDuration => F[Boolean]): F[this.type] = summon[CpsMonad[F]].pure(onTimeoutAsync(t)(f)) + + inline def apply(inline pf: PartialFunction[Any,Boolean]): Unit = + ${ + Select.loopImpl[F]('pf, '{summonInline[CpsSchedulingMonad[F]]}, 'api ) + } + def runAsync(): F[Unit] = async[F] { try while{ diff --git a/shared/src/test/scala/gopher/channels/SelectSuite.scala b/shared/src/test/scala/gopher/channels/SelectSuite.scala index 1f5587a3..a7ca1714 100644 --- a/shared/src/test/scala/gopher/channels/SelectSuite.scala +++ b/shared/src/test/scala/gopher/channels/SelectSuite.scala @@ -172,49 +172,53 @@ class SelectSuite extends FunSuite } - /* - test("basic compound select with for syntax") { + + test("basic compound select with loop select syntax") { - import scala.concurrent.ExecutionContext.Implicits.global - import scala.async.Async._ - val channel1 = gopherApi.makeChannel[Int](1) - val channel2 = gopherApi.makeChannel[Int](1) - val channel3 = gopherApi.makeChannel[Int](1) - val channel4 = gopherApi.makeChannel[Int](1) + val channel1 = makeChannel[Int](1) + val channel2 = makeChannel[Int](1) + val channel3 = makeChannel[Int](1) + val channel4 = makeChannel[Int](1) - val producer = channel1.awriteAll(1 to 1000) + val producer = channel1.awriteAll(1 to 1000) - @volatile var q = false + @volatile var q = false - val selector = async { + val selector = async { @volatile var x=0 @volatile var nw=0 @volatile var ch1s=0 //pending // for syntax will be next: - select.loop{ s => - select match { + select.loop{ case ir: channel1.read => channel4.awrite(ir) - ch1s=ir + ch1s=ir + true case iw: channel3.write if (iw==(x+1)) => - {}; nw = nw+1 - case t: time.after => q=true + {}; + nw = nw+1 + true + case t: Time.after if t == (5 milliseconds) => q=true + false } - } + } - async{ - for(c <- channel4) channel2.write(c) - } - await(selector) + val copier = async{ + for(c <- channel4) channel2.write(c) + } + + async{ + await(selector) - assert(q==true) + assert(q==true) + } } - */ + /* From 5b34a94e0f4ebf8232862628750165134a1ff661 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Thu, 31 Dec 2020 01:50:34 +0200 Subject: [PATCH 028/161] ported test from SelectSuite --- .../src/main/scala/gopher/SelectGroup.scala | 3 +++ .../scala/gopher/channels/SelectSuite.scala | 21 +++++++++++-------- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/shared/src/main/scala/gopher/SelectGroup.scala b/shared/src/main/scala/gopher/SelectGroup.scala index 430c6315..1d0c59e9 100644 --- a/shared/src/main/scala/gopher/SelectGroup.scala +++ b/shared/src/main/scala/gopher/SelectGroup.scala @@ -53,6 +53,9 @@ class SelectGroup[F[_]:CpsSchedulingMonad, S](api: Gopher[F]) extends SelectLis def step():F[S] = retval + def runAsync():F[S] = + retval + inline def run(): S = await(step()) /** diff --git a/shared/src/test/scala/gopher/channels/SelectSuite.scala b/shared/src/test/scala/gopher/channels/SelectSuite.scala index a7ca1714..4e035bd7 100644 --- a/shared/src/test/scala/gopher/channels/SelectSuite.scala +++ b/shared/src/test/scala/gopher/channels/SelectSuite.scala @@ -220,20 +220,23 @@ class SelectSuite extends FunSuite } - /* - test("basic select.once with reading syntax sugar") { + test("basic select.group with reading syntax sugar") { - val channel1 = gopherApi.makeChannel[String](1) - val channel2 = gopherApi.makeChannel[String](1) - val selector = (gopherApi.select.once.reading(channel1)(x=>x) - .reading(channel2)(x=>x) - ).go - channel2.awrite("A") - assert(Await.result(selector, 10.second)=="A") + async { + val channel1 = makeChannel[String](1) + val channel2 = makeChannel[String](1) + val selector = select.group[String].onRead(channel1)(x=>x) + .onRead(channel2)(x=>x) + .runAsync() + channel2.awrite("A") + val r = await(selector) + assert(r=="A") + } } + /* test("basic select.once with writing syntax sugar") { val channel1 = gopherApi.makeChannel[Int](100) val channel2 = gopherApi.makeChannel[Int](100) From 9a99a6f4a1413c0faf900b6afdfa0c389a1f889b Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Fri, 1 Jan 2021 10:33:31 +0200 Subject: [PATCH 029/161] SelectSuite fully ported --- shared/src/main/scala/gopher/Select.scala | 3 + .../scala/gopher/channels/SelectSuite.scala | 93 ++++++++++++------- 2 files changed, 64 insertions(+), 32 deletions(-) diff --git a/shared/src/main/scala/gopher/Select.scala b/shared/src/main/scala/gopher/Select.scala index 6c530453..84b82dc8 100644 --- a/shared/src/main/scala/gopher/Select.scala +++ b/shared/src/main/scala/gopher/Select.scala @@ -16,8 +16,11 @@ class Select[F[_]:CpsSchedulingMonad](api: Gopher[F]): def group[S]: SelectGroup[F,S] = new SelectGroup[F,S](api) + def once[S]: SelectGroup[F,S] = new SelectGroup[F,S](api) + def loop: SelectLoop[F] = new SelectLoop[F](api) + object Select: sealed trait SelectGroupExpr[F[_],S]: diff --git a/shared/src/test/scala/gopher/channels/SelectSuite.scala b/shared/src/test/scala/gopher/channels/SelectSuite.scala index 4e035bd7..b8d2624c 100644 --- a/shared/src/test/scala/gopher/channels/SelectSuite.scala +++ b/shared/src/test/scala/gopher/channels/SelectSuite.scala @@ -236,54 +236,83 @@ class SelectSuite extends FunSuite } } - /* + test("basic select.once with writing syntax sugar") { - val channel1 = gopherApi.makeChannel[Int](100) - val channel2 = gopherApi.makeChannel[Int](100) - @volatile var s:Int = 0 - val selector = (gopherApi.select.once.writing(channel1,s){q:Int =>"A"} - .writing(channel2,s){s=>"B"} - ).go - // hi, Captain Obvious - assert(Set("A","B") contains Await.result(selector, 10.second) ) - channel1.close() - channel2.close() + async { + val channel1 = makeChannel[Int](100) + val channel2 = makeChannel[Int](100) + @volatile var s:Int = 0 + val selector = (select.group.onWrite(channel1,s){ (q:Int) =>"A"} + .onWrite(channel2,s){s=>"B"} + ).runAsync() + //println("before awaiting selector") + val r = await(selector) + //println("after awaiting selector") + + // hi, Captain Obvious + assert(Set("A","B") contains r ) + channel1.close() + channel2.close() + } } + + test("basic select.once with idle syntax sugar") { - val ch = gopherApi.makeChannel[String](1) - val selector = (gopherApi.select.once[String].reading(ch)(x=>x) - .idle("IDLE") - ).go - assert(Await.result(selector, 10.second)=="IDLE") - ch.close() + async{ + val ch = makeChannel[String](1) + val selector = (select.once[String].onRead(ch)(x=>x) + .onTimeout(5 milliseconds)(t => "IDLE") + ).runAsync() + val r = await(selector) + assert(r=="IDLE") + ch.close() + } } + test("basic select.foreach with partial-function syntax sugar") { - val info = gopherApi.makeChannel[Long](1) - val quit = gopherApi.makeChannel[Int](2) - @volatile var (x,y)=(0L,1L) - val writer = gopherApi.select.forever{ + val info = makeChannel[Long](1) + val quit = makeChannel[Int](2) + @volatile var (x,y)=(0L,1L) + + val writer = async { + select.loop{ case z:info.write if (z==x) => x = y y = y + x + true case q:quit.read => - implicitly[FlowTermination[Unit]].doExit(()) - } - @volatile var sum=0L - val reader = gopherApi.select.forever{ + false + } + } + + @volatile var sum=0L + val reader = { + //implicit val printCode = cps.macroFlags.PrintCode + //implicit val debugLevel = cps.macroFlags.DebugLevel(20) + async{ + select.loop{ case z:info.read => sum += z if (sum > 100000) { - quit.write(1) - implicitly[FlowTermination[Unit]].doExit(()) - } + //quit.write(1) + await(quit.awrite(1)) + false + } else { + true + } } - Await.ready(writer, 10.second) - Await.ready(reader, 10.second) - assert(sum > 100000) + } + } + + async{ + await(writer) + await(reader) + assert(sum > 100000) + } } - */ + } From 3b9392dc897e58ed886e422c87daabdfb6d70d49 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Fri, 1 Jan 2021 20:38:18 +0200 Subject: [PATCH 030/161] ported ExpireChannel --- js/src/main/scala/gopher/JSGopher.scala | 18 +++++----- .../main/scala/gopher/impl/BaseChannel.scala | 5 ++- jvm/src/main/scala/gopher/JVMGopher.scala | 18 +++++----- shared/src/main/scala/gopher/Channel.scala | 2 +- .../scala/gopher/ChannelWithExpiration.scala | 32 +++++++++++++++++ .../src/main/scala/gopher/DuppedInput.scala | 6 ++-- shared/src/main/scala/gopher/Gopher.scala | 8 ++--- .../src/main/scala/gopher/ReadChannel.scala | 4 +-- .../src/main/scala/gopher/WriteChannel.scala | 13 +++++-- .../gopher/WriteChannelWithExpiration.scala | 32 +++++++++++++++++ .../gopher/impl/WriterWithExpireTime.scala | 36 +++++++++++++++++++ .../scala/gopher/channels/SelectSuite.scala | 2 +- 12 files changed, 141 insertions(+), 35 deletions(-) create mode 100644 shared/src/main/scala/gopher/ChannelWithExpiration.scala create mode 100644 shared/src/main/scala/gopher/WriteChannelWithExpiration.scala create mode 100644 shared/src/main/scala/gopher/impl/WriterWithExpireTime.scala diff --git a/js/src/main/scala/gopher/JSGopher.scala b/js/src/main/scala/gopher/JSGopher.scala index 103383af..541c32f9 100644 --- a/js/src/main/scala/gopher/JSGopher.scala +++ b/js/src/main/scala/gopher/JSGopher.scala @@ -7,17 +7,15 @@ import scala.concurrent.duration._ class JSGopher[F[_]:CpsSchedulingMonad](cfg: JSGopherConfig) extends Gopher[F]: - def makeChannel[A](bufSize:Int = 0, autoClose: Boolean = false, expire: Duration = Duration.Inf) = - if (expire == Duration.Inf ) - if (!autoClose) then - if (bufSize == 0) then - impl.UnbufferedChannel[F,A](this) - else - impl.BufferedChannel[F,A](this,bufSize) - else - impl.PromiseChannel[F,A](this) + def makeChannel[A](bufSize:Int = 0, autoClose: Boolean = false) = + if (!autoClose) then + if (bufSize == 0) then + impl.UnbufferedChannel[F,A](this) + else + impl.BufferedChannel[F,A](this,bufSize) else - ??? + impl.PromiseChannel[F,A](this) + val time = new impl.JSTime(this) diff --git a/js/src/main/scala/gopher/impl/BaseChannel.scala b/js/src/main/scala/gopher/impl/BaseChannel.scala index c86f6251..d2482a8e 100644 --- a/js/src/main/scala/gopher/impl/BaseChannel.scala +++ b/js/src/main/scala/gopher/impl/BaseChannel.scala @@ -77,7 +77,10 @@ abstract class BaseChannel[F[_],A](override val gopherApi: JSGopher[F]) extends v.markUsed() action(a) case None => - throw DeadlockDetected() + // do nothing. + // exists case, when this is possible: wheb we close channel from + // select-group callback, which is evaluated now. + // in this case we will see one as evaluating. } protected def processCloseDone(): Unit = diff --git a/jvm/src/main/scala/gopher/JVMGopher.scala b/jvm/src/main/scala/gopher/JVMGopher.scala index 7759d3c8..b06d5013 100644 --- a/jvm/src/main/scala/gopher/JVMGopher.scala +++ b/jvm/src/main/scala/gopher/JVMGopher.scala @@ -14,17 +14,15 @@ import scala.concurrent.duration._ class JVMGopher[F[_]:CpsSchedulingMonad](cfg: JVMGopherConfig) extends Gopher[F]: - def makeChannel[A](bufSize:Int = 0, autoClose: Boolean = false, ttl: Duration = Duration.Inf) = - if (ttl == Duration.Inf) then - if autoClose then - PromiseChannel[F,A](this, taskExecutor) - else - if (bufSize == 0) - GuardedSPSCUnbufferedChannel[F,A](this, cfg.controlExecutor,cfg.taskExecutor) - else - GuardedSPSCBufferedChannel[F,A](this, bufSize, cfg.controlExecutor,cfg.taskExecutor) + def makeChannel[A](bufSize:Int = 0, autoClose: Boolean = false) = + if autoClose then + PromiseChannel[F,A](this, taskExecutor) else - ??? + if (bufSize == 0) + GuardedSPSCUnbufferedChannel[F,A](this, cfg.controlExecutor,cfg.taskExecutor) + else + GuardedSPSCBufferedChannel[F,A](this, bufSize, cfg.controlExecutor,cfg.taskExecutor) + val time = new JVMTime(this) diff --git a/shared/src/main/scala/gopher/Channel.scala b/shared/src/main/scala/gopher/Channel.scala index 9f809dd8..1b02bb97 100644 --- a/shared/src/main/scala/gopher/Channel.scala +++ b/shared/src/main/scala/gopher/Channel.scala @@ -5,7 +5,7 @@ import java.io.Closeable trait Channel[F[_],W,R] extends WriteChannel[F,W] with ReadChannel[F,R] with Closeable: - override protected def gopherApi: Gopher[F] + override def gopherApi: Gopher[F] end Channel diff --git a/shared/src/main/scala/gopher/ChannelWithExpiration.scala b/shared/src/main/scala/gopher/ChannelWithExpiration.scala new file mode 100644 index 00000000..15425f67 --- /dev/null +++ b/shared/src/main/scala/gopher/ChannelWithExpiration.scala @@ -0,0 +1,32 @@ +package gopher + +import cps._ +import gopher.impl._ +import scala.concurrent.duration.FiniteDuration + +class ChannelWithExpiration[F[_],W,R](internal: Channel[F,W,R], ttl: FiniteDuration) + extends WriteChannelWithExpiration[F,W](internal, ttl) + with Channel[F,W,R]: + + + override def gopherApi: Gopher[F] = internal.gopherApi + + override def asyncMonad: CpsSchedulingMonad[F] = gopherApi.asyncMonad + + override def addReader(reader: Reader[R]): Unit = + internal.addReader(reader) + + override def addDoneReader(reader: Reader[Unit]): Unit = + internal.addDoneReader(reader) + + + override def withExpiration(ttl: FiniteDuration): ChannelWithExpiration[F,W,R] = + new ChannelWithExpiration(internal , ttl) + + + override def close(): Unit = internal.close() + + + def qqq: Int = 0 + + \ No newline at end of file diff --git a/shared/src/main/scala/gopher/DuppedInput.scala b/shared/src/main/scala/gopher/DuppedInput.scala index 6380adae..67256cac 100644 --- a/shared/src/main/scala/gopher/DuppedInput.scala +++ b/shared/src/main/scala/gopher/DuppedInput.scala @@ -11,13 +11,13 @@ import java.util.concurrent.atomic.AtomicInteger -class DuppedInput[F[_],A](origin:ReadChannel[F,A], bufSize: Int=1, expiration: Duration = Duration.Inf)(using api:Gopher[F]) +class DuppedInput[F[_],A](origin:ReadChannel[F,A], bufSize: Int=1)(using api:Gopher[F]) { def pair = (sink1, sink2) - val sink1 = makeChannel[A](bufSize,false,expiration) - val sink2 = makeChannel[A](bufSize,false,expiration) + val sink1 = makeChannel[A](bufSize,false) + val sink2 = makeChannel[A](bufSize,false) given CpsSchedulingMonad[F] = api.asyncMonad diff --git a/shared/src/main/scala/gopher/Gopher.scala b/shared/src/main/scala/gopher/Gopher.scala index 348500d4..016bd06f 100644 --- a/shared/src/main/scala/gopher/Gopher.scala +++ b/shared/src/main/scala/gopher/Gopher.scala @@ -10,8 +10,7 @@ trait Gopher[F[_]:CpsSchedulingMonad]: def asyncMonad: CpsSchedulingMonad[F] = summon[CpsSchedulingMonad[F]] def makeChannel[A](bufSize:Int = 0, - autoClose: Boolean = false, - expire: Duration = Duration.Inf): Channel[F,A,A] + autoClose: Boolean = false): Channel[F,A,A] def makeOnceChannel[A](): Channel[F,A,A] = makeChannel[A](1,true) @@ -23,9 +22,8 @@ trait Gopher[F[_]:CpsSchedulingMonad]: def makeChannel[A](bufSize:Int = 0, - autoClose: Boolean = false, - expire: Duration = Duration.Inf)(using g:Gopher[?]):Channel[g.Monad,A,A] = - g.makeChannel(bufSize, autoClose, expire) + autoClose: Boolean = false)(using g:Gopher[?]):Channel[g.Monad,A,A] = + g.makeChannel(bufSize, autoClose) def makeOnceChannel[A]()(using g:Gopher[?]): Channel[g.Monad,A,A] = g.makeOnceChannel[A]() diff --git a/shared/src/main/scala/gopher/ReadChannel.scala b/shared/src/main/scala/gopher/ReadChannel.scala index cac33dfa..d1bd30a3 100644 --- a/shared/src/main/scala/gopher/ReadChannel.scala +++ b/shared/src/main/scala/gopher/ReadChannel.scala @@ -15,7 +15,7 @@ trait ReadChannel[F[_], A]: protected def gopherApi: Gopher[F] - protected def asyncMonad: CpsSchedulingMonad[F] = gopherApi.asyncMonad + def asyncMonad: CpsSchedulingMonad[F] = gopherApi.asyncMonad // workarround for https://github.com/lampepfl/dotty/issues/10477 protected def rAsyncMonad: CpsAsyncMonad[F] = asyncMonad @@ -95,7 +95,7 @@ trait ReadChannel[F[_], A]: await(aforeach(f))(using rAsyncMonad) def dup(bufSize: Int=1, expiration: Duration=Duration.Inf): (ReadChannel[F,A], ReadChannel[F,A]) = - DuppedInput(this, bufSize, expiration)(using gopherApi).pair + DuppedInput(this, bufSize)(using gopherApi).pair class DoneReadChannel extends ReadChannel[F,Unit]: diff --git a/shared/src/main/scala/gopher/WriteChannel.scala b/shared/src/main/scala/gopher/WriteChannel.scala index da6d5d15..cd8c8d66 100644 --- a/shared/src/main/scala/gopher/WriteChannel.scala +++ b/shared/src/main/scala/gopher/WriteChannel.scala @@ -3,14 +3,15 @@ package gopher import cps._ import gopher.impl._ -import scala.util.Try import scala.annotation.targetName +import scala.concurrent.duration.FiniteDuration +import scala.util.Try trait WriteChannel[F[_], A]: type write = A - protected def asyncMonad: CpsAsyncMonad[F] + def asyncMonad: CpsAsyncMonad[F] def awrite(a:A):F[Unit] = asyncMonad.adoptCallbackStyle(f => @@ -32,6 +33,7 @@ trait WriteChannel[F[_], A]: // inline def unapply(y:Any): Option[A] = // Some(x) + //TODO: make protected[gopher] def addWriter(writer: Writer[A]): Unit def awriteAll(collection: IterableOnce[A]): F[Unit] = @@ -46,6 +48,10 @@ trait WriteChannel[F[_], A]: inline def writeAll(collection: IterableOnce[A]): Unit = await(awriteAll(collection))(using asyncMonad) + + def withExpiration(ttl: FiniteDuration): WriteChannelWithExpiration[F,A] = + new WriteChannelWithExpiration(this, ttl) + class SimpleWriter(a:A, f: Try[Unit]=>Unit) extends Writer[A]: def canExpire: Boolean = false @@ -58,3 +64,6 @@ trait WriteChannel[F[_], A]: def markFree(): Unit = () + + + diff --git a/shared/src/main/scala/gopher/WriteChannelWithExpiration.scala b/shared/src/main/scala/gopher/WriteChannelWithExpiration.scala new file mode 100644 index 00000000..0455971e --- /dev/null +++ b/shared/src/main/scala/gopher/WriteChannelWithExpiration.scala @@ -0,0 +1,32 @@ +package gopher + +import cps._ +import scala.concurrent._ +import scala.concurrent.duration._ +import scala.util._ +import gopher.impl._ + + +/** + * Channel, where messages can be exprited. + **/ +class WriteChannelWithExpiration[F[_],A](internal: WriteChannel[F,A], ttl: FiniteDuration) extends WriteChannel[F,A]: + + override def awrite(a:A):F[Unit] = + val expireTime = System.currentTimeMillis() + ttl.toMillis + asyncMonad.adoptCallbackStyle(f => + internal.addWriter(SimpleWriterWithExpireTime(a, f, expireTime)) + ) + + def addWriter(writer: Writer[A]): Unit = + val expireTime = System.currentTimeMillis() + ttl.toMillis + internal.addWriter(NesteWriterWithExpireTime(writer,expireTime)) + + def asyncMonad: CpsAsyncMonad[F] = + internal.asyncMonad + + override def withExpiration(ttl: FiniteDuration): WriteChannelWithExpiration[F,A] = + new WriteChannelWithExpiration(internal, ttl) + + + diff --git a/shared/src/main/scala/gopher/impl/WriterWithExpireTime.scala b/shared/src/main/scala/gopher/impl/WriterWithExpireTime.scala new file mode 100644 index 00000000..de64a3cb --- /dev/null +++ b/shared/src/main/scala/gopher/impl/WriterWithExpireTime.scala @@ -0,0 +1,36 @@ +package gopher.impl + +import scala.util.Try + +class SimpleWriterWithExpireTime[A](a:A, f: Try[Unit] => Unit, expireTimeMillis: Long) extends Writer[A]: + + def canExpire: Boolean = true + + def isExpired: Boolean = + //TODO: way to mock current time + System.currentTimeMillis >= expireTimeMillis + + def capture(): Option[(A,Try[Unit]=>Unit)] = Some((a,f)) + + def markUsed(): Unit = () + + def markFree(): Unit = () + + // TODO: pass time source +class NesteWriterWithExpireTime[A](nested: Writer[A], expireTimeMillis: Long) extends Writer[A]: + + def canExpire: Boolean = true + + def isExpired: Boolean = + (System.currentTimeMillis >= expireTimeMillis) || nested.isExpired + + def capture(): Option[(A,Try[Unit]=>Unit)] = + if (isExpired) None else nested.capture() + + def markUsed(): Unit = nested.markUsed() + + def markFree(): Unit = nested.markFree() + + + + \ No newline at end of file diff --git a/shared/src/test/scala/gopher/channels/SelectSuite.scala b/shared/src/test/scala/gopher/channels/SelectSuite.scala index b8d2624c..a9f3bdb6 100644 --- a/shared/src/test/scala/gopher/channels/SelectSuite.scala +++ b/shared/src/test/scala/gopher/channels/SelectSuite.scala @@ -258,7 +258,7 @@ class SelectSuite extends FunSuite - test("basic select.once with idle syntax sugar") { + test("basic select.once with idle syntax sugar".only) { async{ val ch = makeChannel[String](1) val selector = (select.once[String].onRead(ch)(x=>x) From 88820bd8ab20f501dd3605576dada4406630a037 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Sat, 2 Jan 2021 19:15:25 +0200 Subject: [PATCH 031/161] implemented Ticker and channesl with expiration --- .../gopher/channels/ExpireChannelSuite.scala | 53 ----------- build.sbt | 1 + js/src/main/scala/gopher/JSGopher.scala | 15 ++++ .../main/scala/gopher/impl/BaseChannel.scala | 2 + jvm/src/main/scala/gopher/JVMGopher.scala | 25 +++++- shared/src/main/scala/gopher/Channel.scala | 5 ++ .../scala/gopher/ChannelWithExpiration.scala | 10 +-- shared/src/main/scala/gopher/Gopher.scala | 15 +++- shared/src/main/scala/gopher/GopherAPI.scala | 1 - .../src/main/scala/gopher/SelectGroup.scala | 3 + shared/src/main/scala/gopher/Time.scala | 55 +++++++----- .../src/main/scala/gopher/WriteChannel.scala | 15 +--- .../gopher/WriteChannelWithExpiration.scala | 23 +++-- .../src/main/scala/gopher/impl/Writer.scala | 14 +++ .../gopher/impl/WriterWithExpireTime.scala | 46 +++++++++- .../gopher/channels/ExpireChannelSuite.scala | 90 +++++++++++++++++++ .../gopher/channels/FutureWithTimeout.scala | 22 +++++ .../scala/gopher/channels/SelectSuite.scala | 1 - 18 files changed, 290 insertions(+), 106 deletions(-) delete mode 100644 0.99.x/src/test/scala/gopher/channels/ExpireChannelSuite.scala create mode 100644 shared/src/test/scala/gopher/channels/ExpireChannelSuite.scala create mode 100644 shared/src/test/scala/gopher/channels/FutureWithTimeout.scala diff --git a/0.99.x/src/test/scala/gopher/channels/ExpireChannelSuite.scala b/0.99.x/src/test/scala/gopher/channels/ExpireChannelSuite.scala deleted file mode 100644 index 2fa7f7c2..00000000 --- a/0.99.x/src/test/scala/gopher/channels/ExpireChannelSuite.scala +++ /dev/null @@ -1,53 +0,0 @@ -package gopher.channels - -import org.scalatest.AsyncFunSuite - -import scala.concurrent.TimeoutException -import scala.concurrent.duration._ -import scala.language.postfixOps - -class ExpireChannelSuite extends AsyncFunSuite { - - import CommonTestObjects._ - - test("if message not readed, it expires") { - val ch = gopherApi.make[ExpireChannel[Int]](300 milliseconds, 10) - val empty = for {_ <- ch.awrite(1) - _ <- gopherApi.time.asleep(400 milliseconds) - r <- ch.aread - } yield r - recoverToSucceededIf[TimeoutException]{ - empty.withTimeout(300 milliseconds) - } - } - - test("before expire we can read message") { - val ch = gopherApi.make[ExpireChannel[Int]](300 milliseconds, 10) - for { - _ <- ch.awrite(1) - _ <- gopherApi.time.asleep(10 milliseconds) - r <- ch.aread - } yield assert(r==1) - } - - test("unbuffered expriew channel: return from write when value expired") { - val ch = gopherApi.make[ExpireChannel[Int]](300 milliseconds, 0) - ch.awrite(1).withTimeout(2 seconds).map(x => assert(x == 1) ) - } - - test("expire must be an order") { - val ch = gopherApi.make[ExpireChannel[Int]](300 milliseconds, 10) - val fr1 = ch.aread - val fr2 = ch.aread - for { - _ <- ch.awriteAll(List(1,2)) - _ <- gopherApi.time.asleep(10 milliseconds) - fr3 = ch.aread - r3 <- recoverToSucceededIf[TimeoutException]( fr3.withTimeout(1 second) ) - w4 <- ch.awrite(4) - r31 <- fr3 - } yield assert(r31 == 4) - } - - -} diff --git a/build.sbt b/build.sbt index 0398ab4d..b9594ec3 100644 --- a/build.sbt +++ b/build.sbt @@ -32,6 +32,7 @@ lazy val gopher = crossProject(JSPlatform, JVMPlatform) .jvmSettings( scalacOptions ++= Seq( "-unchecked", "-Ycheck:macro" ), ).jsSettings( + libraryDependencies += ("org.scala-js" %%% "scalajs-java-logging" % "1.0.0").withDottyCompat(scalaVersion.value), // TODO: switch to ModuleES ? scalaJSLinkerConfig ~= { _.withModuleKind(ModuleKind.CommonJSModule) }, scalaJSUseMainModuleInitializer := true, diff --git a/js/src/main/scala/gopher/JSGopher.scala b/js/src/main/scala/gopher/JSGopher.scala index 541c32f9..98690006 100644 --- a/js/src/main/scala/gopher/JSGopher.scala +++ b/js/src/main/scala/gopher/JSGopher.scala @@ -2,6 +2,7 @@ package gopher import cps._ import java.util.Timer +import java.util.logging._ import scala.concurrent.duration._ class JSGopher[F[_]:CpsSchedulingMonad](cfg: JSGopherConfig) extends Gopher[F]: @@ -19,6 +20,20 @@ class JSGopher[F[_]:CpsSchedulingMonad](cfg: JSGopherConfig) extends Gopher[F]: val time = new impl.JSTime(this) + def setLogFun(logFun:(Level, String, Throwable|Null) => Unit): ((Level, String, Throwable|Null) => Unit) = + val r = currentLogFun + currentLogFun = logFun + r + + def log(level: Level, message: String, ex: Throwable| Null): Unit = + currentLogFun.apply(level,message,ex) + + private var currentLogFun: (Level, String, Throwable|Null )=> Unit = { (level,message,ex) => + System.err.println(s"${level}:${message}"); + if !(ex eq null) then + ex.nn.printStackTrace() + } + object JSGopher extends GopherAPI: diff --git a/js/src/main/scala/gopher/impl/BaseChannel.scala b/js/src/main/scala/gopher/impl/BaseChannel.scala index d2482a8e..3df207aa 100644 --- a/js/src/main/scala/gopher/impl/BaseChannel.scala +++ b/js/src/main/scala/gopher/impl/BaseChannel.scala @@ -6,6 +6,7 @@ import scala.collection.mutable.Queue import scala.scalajs.concurrent.JSExecutionContext import scala.util._ import scala.util.control.NonFatal +import java.util.logging.Level abstract class BaseChannel[F[_],A](override val gopherApi: JSGopher[F]) extends Channel[F,A,A]: @@ -26,6 +27,7 @@ abstract class BaseChannel[F[_],A](override val gopherApi: JSGopher[F]) extends catch case NonFatal(ex) => if (true) then + gopherApi.log(Level.WARNING, "impossible: exception in channel callback", ex) ex.printStackTrace() if (false) then JSExecutionContext.queue.execute( ()=> throw ex ) diff --git a/jvm/src/main/scala/gopher/JVMGopher.scala b/jvm/src/main/scala/gopher/JVMGopher.scala index b06d5013..b497479d 100644 --- a/jvm/src/main/scala/gopher/JVMGopher.scala +++ b/jvm/src/main/scala/gopher/JVMGopher.scala @@ -6,7 +6,9 @@ import gopher.impl._ import java.util.concurrent.Executors import java.util.concurrent.ExecutorService import java.util.concurrent.ForkJoinPool +import java.util.concurrent.atomic.AtomicReference import java.util.Timer +import java.util.logging._ import scala.concurrent.duration._ @@ -22,15 +24,25 @@ class JVMGopher[F[_]:CpsSchedulingMonad](cfg: JVMGopherConfig) extends Gopher[F] GuardedSPSCUnbufferedChannel[F,A](this, cfg.controlExecutor,cfg.taskExecutor) else GuardedSPSCBufferedChannel[F,A](this, bufSize, cfg.controlExecutor,cfg.taskExecutor) - - + + val time = new JVMTime(this) + def setLogFun(logFun:(Level, String, Throwable|Null) => Unit): ((Level, String, Throwable|Null) => Unit) = + currentLogFun.getAndSet(logFun) + + def log(level: Level, message: String, ex: Throwable| Null): Unit = + currentLogFun.get().apply(level,message,ex) + def taskExecutor = cfg.taskExecutor def scheduledExecutor = JVMGopher.scheduledExecutor + + private val currentLogFun: AtomicReference[(Level,String,Throwable|Null)=>Unit]=new AtomicReference(JVMGopher.defaultLogFun) + + object JVMGopher extends GopherAPI: @@ -50,3 +62,12 @@ object JVMGopher extends GopherAPI: taskExecutor=ForkJoinPool.commonPool(), ) + val logger = Logger.getLogger("JVMGopher") + + def defaultLogFun(level: Level, message:String, ex: Throwable|Null): Unit = + if (ex eq null) { + logger.log(level, message) + } else { + logger.log(level, message, ex) + } + \ No newline at end of file diff --git a/shared/src/main/scala/gopher/Channel.scala b/shared/src/main/scala/gopher/Channel.scala index 1b02bb97..8bd9c562 100644 --- a/shared/src/main/scala/gopher/Channel.scala +++ b/shared/src/main/scala/gopher/Channel.scala @@ -2,10 +2,15 @@ package gopher import cps._ import java.io.Closeable +import scala.concurrent.duration.FiniteDuration trait Channel[F[_],W,R] extends WriteChannel[F,W] with ReadChannel[F,R] with Closeable: override def gopherApi: Gopher[F] + def withExpiration(ttl: FiniteDuration, throwTimeouts: Boolean): ChannelWithExpiration[F,W,R] = + new ChannelWithExpiration(this, ttl, throwTimeouts) + + end Channel diff --git a/shared/src/main/scala/gopher/ChannelWithExpiration.scala b/shared/src/main/scala/gopher/ChannelWithExpiration.scala index 15425f67..811752ca 100644 --- a/shared/src/main/scala/gopher/ChannelWithExpiration.scala +++ b/shared/src/main/scala/gopher/ChannelWithExpiration.scala @@ -4,8 +4,8 @@ import cps._ import gopher.impl._ import scala.concurrent.duration.FiniteDuration -class ChannelWithExpiration[F[_],W,R](internal: Channel[F,W,R], ttl: FiniteDuration) - extends WriteChannelWithExpiration[F,W](internal, ttl) +class ChannelWithExpiration[F[_],W,R](internal: Channel[F,W,R], ttl: FiniteDuration, throwTimeouts: Boolean) + extends WriteChannelWithExpiration[F,W](internal, ttl, throwTimeouts, internal.gopherApi) with Channel[F,W,R]: @@ -19,9 +19,9 @@ class ChannelWithExpiration[F[_],W,R](internal: Channel[F,W,R], ttl: FiniteDurat override def addDoneReader(reader: Reader[Unit]): Unit = internal.addDoneReader(reader) - - override def withExpiration(ttl: FiniteDuration): ChannelWithExpiration[F,W,R] = - new ChannelWithExpiration(internal , ttl) + + override def withExpiration(ttl: FiniteDuration, throwTimeouts: Boolean): ChannelWithExpiration[F,W,R] = + new ChannelWithExpiration(internal , ttl, throwTimeouts) override def close(): Unit = internal.close() diff --git a/shared/src/main/scala/gopher/Gopher.scala b/shared/src/main/scala/gopher/Gopher.scala index 016bd06f..aeb58a43 100644 --- a/shared/src/main/scala/gopher/Gopher.scala +++ b/shared/src/main/scala/gopher/Gopher.scala @@ -3,6 +3,8 @@ package gopher import cps._ import scala.concurrent.duration.Duration +import java.util.logging.{Level => LogLevel} + trait Gopher[F[_]:CpsSchedulingMonad]: @@ -19,7 +21,17 @@ trait Gopher[F[_]:CpsSchedulingMonad]: new Select[F](this) def time: Time[F] - + + def setLogFun(logFun:(LogLevel, String, Throwable|Null) => Unit): ((LogLevel, String, Throwable|Null) => Unit) + + def log(level: LogLevel, message: String, ex: Throwable| Null): Unit + + def log(level: LogLevel, message: String): Unit = + log(level,message, null) + + protected[gopher] def logImpossible(ex: Throwable): Unit = + log(LogLevel.WARNING, "impossible", ex) + def makeChannel[A](bufSize:Int = 0, autoClose: Boolean = false)(using g:Gopher[?]):Channel[g.Monad,A,A] = @@ -28,7 +40,6 @@ def makeChannel[A](bufSize:Int = 0, def makeOnceChannel[A]()(using g:Gopher[?]): Channel[g.Monad,A,A] = g.makeOnceChannel[A]() - def select(using g:Gopher[?]):Select[g.Monad] = g.select diff --git a/shared/src/main/scala/gopher/GopherAPI.scala b/shared/src/main/scala/gopher/GopherAPI.scala index 8c88b0b2..7b79078c 100644 --- a/shared/src/main/scala/gopher/GopherAPI.scala +++ b/shared/src/main/scala/gopher/GopherAPI.scala @@ -31,7 +31,6 @@ object SharedGopherAPI { initPlatformSpecific() _api.get - private[gopher] def setApi(api: GopherAPI): Unit = this._api = Some(api) diff --git a/shared/src/main/scala/gopher/SelectGroup.scala b/shared/src/main/scala/gopher/SelectGroup.scala index 1d0c59e9..94fa4cd5 100644 --- a/shared/src/main/scala/gopher/SelectGroup.scala +++ b/shared/src/main/scala/gopher/SelectGroup.scala @@ -21,6 +21,9 @@ class SelectGroup[F[_]:CpsSchedulingMonad, S](api: Gopher[F]) extends SelectLis /** * instance of select group created for call of select. + * 0 - free + * 1 - now processes + * 2 - expired **/ val waitState: AtomicInteger = new AtomicInteger(0) var call: Try[S] => Unit = { _ => () } diff --git a/shared/src/main/scala/gopher/Time.scala b/shared/src/main/scala/gopher/Time.scala index f4603ee0..30630a93 100644 --- a/shared/src/main/scala/gopher/Time.scala +++ b/shared/src/main/scala/gopher/Time.scala @@ -1,9 +1,9 @@ package gopher import cps._ +import gopher.impl._ - - +import scala.concurrent._ import scala.concurrent.duration._ import java.util.concurrent.TimeUnit @@ -11,7 +11,7 @@ import scala.language.experimental.macros import scala.util.Try import scala.util.Failure import scala.util.Success -import java.util.concurrent.atomic.AtomicReference +import java.util.concurrent.atomic.AtomicBoolean import java.util.TimerTask @@ -21,6 +21,7 @@ import java.util.TimerTask */ abstract class Time[F[_]](gopherAPI: Gopher[F]) { + type after = FiniteDuration def after(duration: FiniteDuration): ReadChannel[F,FiniteDuration] = { @@ -58,37 +59,45 @@ abstract class Time[F[_]](gopherAPI: Gopher[F]) { */ def tick(duration: FiniteDuration): ReadChannel[F,FiniteDuration] = { - ??? - //newTicker(duration) + newTicker(duration).channel } - /* - class Ticker(duration: FiniteDuration) - { + + class Ticker(duration: FiniteDuration) { - val ch = ExpireChannel[Instant](duration,0)(gopherAPI) - val cancellable = gopherAPI.actorSystem.scheduler.schedule(duration,duration)(tick)(ec) - gopherAPI.actorSystem.registerOnTermination{ - if (!cancellable.isCancelled) cancellable.cancel() + val channel = gopherAPI.makeChannel[FiniteDuration](0).withExpiration(duration, false) + + private val scheduled = schedule(tick, duration) + private val stopped = AtomicBoolean(false) + + def stop(): Unit = { + scheduled.cancel() + stopped.set(true) } - def tick():Unit = { - if (!cancellable.isCancelled) { - ch.awrite(Instant.now()).onComplete{ - case Failure(ex:ChannelClosedException) => cancellable.cancel() - case Failure(ex) => cancellable.cancel() // strange, but stop. - case _ => - }(ec) - } + private def tick():Unit = { + if (!stopped.get()) then + channel.addWriter(SimpleWriter(now(),{ + case Success(_) => // ok, somebody readed + case Failure(ex) => + ex match + case ex: ChannelClosedException => + scheduled.cancel() + stopped.lazySet(true) + case ex: TimeoutException => // + case other => // impossible, + gopherAPI.logImpossible(other) + })) + schedule(tick, duration) } } - def newTicker(duration: FiniteDuration): Channel[Instant] = + def newTicker(duration: FiniteDuration): Ticker = { - (new Ticker(duration)).ch + new Ticker(duration) } - */ + def now(): FiniteDuration = FiniteDuration(System.currentTimeMillis(),TimeUnit.MILLISECONDS) diff --git a/shared/src/main/scala/gopher/WriteChannel.scala b/shared/src/main/scala/gopher/WriteChannel.scala index cd8c8d66..56f703e4 100644 --- a/shared/src/main/scala/gopher/WriteChannel.scala +++ b/shared/src/main/scala/gopher/WriteChannel.scala @@ -49,20 +49,9 @@ trait WriteChannel[F[_], A]: await(awriteAll(collection))(using asyncMonad) - def withExpiration(ttl: FiniteDuration): WriteChannelWithExpiration[F,A] = - new WriteChannelWithExpiration(this, ttl) + def withWriteExpiration(ttl: FiniteDuration, throwTimeouts: Boolean)(using gopherApi: Gopher[F]): WriteChannelWithExpiration[F,A] = + new WriteChannelWithExpiration(this, ttl, throwTimeouts, gopherApi) - class SimpleWriter(a:A, f: Try[Unit]=>Unit) extends Writer[A]: - - def canExpire: Boolean = false - - def isExpired: Boolean = false - - def capture(): Option[(A,Try[Unit]=>Unit)] = Some((a,f)) - - def markUsed(): Unit = () - - def markFree(): Unit = () diff --git a/shared/src/main/scala/gopher/WriteChannelWithExpiration.scala b/shared/src/main/scala/gopher/WriteChannelWithExpiration.scala index 0455971e..423ca35d 100644 --- a/shared/src/main/scala/gopher/WriteChannelWithExpiration.scala +++ b/shared/src/main/scala/gopher/WriteChannelWithExpiration.scala @@ -10,23 +10,36 @@ import gopher.impl._ /** * Channel, where messages can be exprited. **/ -class WriteChannelWithExpiration[F[_],A](internal: WriteChannel[F,A], ttl: FiniteDuration) extends WriteChannel[F,A]: +class WriteChannelWithExpiration[F[_],A](internal: WriteChannel[F,A], ttl: FiniteDuration, throwTimeouts: Boolean, gopherApi: Gopher[F]) extends WriteChannel[F,A]: + override def awrite(a:A):F[Unit] = val expireTime = System.currentTimeMillis() + ttl.toMillis asyncMonad.adoptCallbackStyle(f => - internal.addWriter(SimpleWriterWithExpireTime(a, f, expireTime)) + internal.addWriter(makeExpirableWriter(a, f, expireTime)) ) def addWriter(writer: Writer[A]): Unit = val expireTime = System.currentTimeMillis() + ttl.toMillis - internal.addWriter(NesteWriterWithExpireTime(writer,expireTime)) + internal.addWriter(wrapExpirable(writer,expireTime)) def asyncMonad: CpsAsyncMonad[F] = internal.asyncMonad - override def withExpiration(ttl: FiniteDuration): WriteChannelWithExpiration[F,A] = - new WriteChannelWithExpiration(internal, ttl) + override def withWriteExpiration(ttl: FiniteDuration, throwTimeouts: Boolean)(using gopherApi: Gopher[F]): WriteChannelWithExpiration[F,A] = + new WriteChannelWithExpiration(internal, ttl, throwTimeouts, gopherApi) + + private def wrapExpirable(nested: Writer[A], expireTimeMillis: Long) = + if (throwTimeouts) then + NestedWriterWithExpireTimeThrowing(nested, expireTimeMillis, gopherApi) + else + NesteWriterWithExpireTime(nested, expireTimeMillis) + + private def makeExpirableWriter(a:A, f: Try[Unit]=>Unit, expireTimeMillis: Long): Writer[A] = + if (throwTimeouts) + NestedWriterWithExpireTimeThrowing(SimpleWriter(a,f), expireTimeMillis, gopherApi) + else + SimpleWriterWithExpireTime(a,f,expireTimeMillis) diff --git a/shared/src/main/scala/gopher/impl/Writer.scala b/shared/src/main/scala/gopher/impl/Writer.scala index 132ead65..13e162c0 100644 --- a/shared/src/main/scala/gopher/impl/Writer.scala +++ b/shared/src/main/scala/gopher/impl/Writer.scala @@ -4,3 +4,17 @@ import scala.util.Try trait Writer[A] extends Expirable[(A,Try[Unit]=>Unit)] + +class SimpleWriter[A](a:A, f: Try[Unit]=>Unit) extends Writer[A]: + + def canExpire: Boolean = false + + def isExpired: Boolean = false + + def capture(): Option[(A,Try[Unit]=>Unit)] = Some((a,f)) + + def markUsed(): Unit = () + + def markFree(): Unit = () + + diff --git a/shared/src/main/scala/gopher/impl/WriterWithExpireTime.scala b/shared/src/main/scala/gopher/impl/WriterWithExpireTime.scala index de64a3cb..e3950895 100644 --- a/shared/src/main/scala/gopher/impl/WriterWithExpireTime.scala +++ b/shared/src/main/scala/gopher/impl/WriterWithExpireTime.scala @@ -1,6 +1,11 @@ package gopher.impl -import scala.util.Try +import gopher._ +import scala.concurrent._ +import scala.concurrent.duration._ +import scala.util._ +import java.util.concurrent.TimeUnit + class SimpleWriterWithExpireTime[A](a:A, f: Try[Unit] => Unit, expireTimeMillis: Long) extends Writer[A]: @@ -31,6 +36,45 @@ class NesteWriterWithExpireTime[A](nested: Writer[A], expireTimeMillis: Long) ex def markFree(): Unit = nested.markFree() +class NestedWriterWithExpireTimeThrowing[F[_],A](nested: Writer[A], expireTimeMillis: Long, gopherApi: Gopher[F]) extends Writer[A]: + + val scheduledThrow = gopherApi.time.schedule( + () => checkExpire(), + FiniteDuration(expireTimeMillis - gopherApi.time.now().toMillis, TimeUnit.MILLISECONDS) + ) + + def canExpire: Boolean = true + + def isExpired: Boolean = + (gopherApi.time.now().toMillis >= expireTimeMillis) || nested.isExpired + + def capture(): Option[(A,Try[Unit]=>Unit)] = + nested.capture() + + def markUsed(): Unit = + scheduledThrow.cancel() + nested.markUsed() + + def markFree(): Unit = + nested.markFree() + checkExpire() + + def checkExpire(): Unit = + if (gopherApi.time.now().toMillis > expireTimeMillis) then + if (!nested.isExpired) then + nested.capture() match + case Some((a,f)) => + nested.markUsed() + try + f(Failure(new TimeoutException())) + catch + case ex: Throwable => + ex.printStackTrace() + case None => + // none, will be colled after markFree is needed. + + + \ No newline at end of file diff --git a/shared/src/test/scala/gopher/channels/ExpireChannelSuite.scala b/shared/src/test/scala/gopher/channels/ExpireChannelSuite.scala new file mode 100644 index 00000000..e8afbcf1 --- /dev/null +++ b/shared/src/test/scala/gopher/channels/ExpireChannelSuite.scala @@ -0,0 +1,90 @@ +package gopher.channels + + +import cps._ +import gopher._ +import cps.monads.FutureAsyncMonad + +import scala.concurrent._ +import scala.concurrent.duration._ +import scala.util._ +import scala.language.postfixOps + +import munit._ + +class ExpireChannelSuite extends FunSuite { + + + import scala.concurrent.ExecutionContext.Implicits.global + given Gopher[Future] = SharedGopherAPI.apply[Future]() + + + test("if message not readed, it expires") { + val ch = makeChannel[Int](10).withExpiration(300 milliseconds, false) + val emptyRead = for {_ <- ch.awrite(1) + _ <- Time.asleep(400 milliseconds) + r <- ch.aread + } yield r + + async { + + try { + await( emptyRead.withTimeout(300 milliseconds) ) + assert("Here" == "should not be accessible") + }catch{ + case ex: TimeoutException => + assert(true) + } + + } + + } + + + test("before expire we can read message") { + val ch = makeChannel[Int](10).withExpiration(300 milliseconds, false) + for { + _ <- ch.awrite(1) + _ <- Time.asleep(10 milliseconds) + r <- ch.aread + } yield assert(r==1) + } + + + + test("unbuffered expriew channel: return from write when value expired") { + val ch = makeChannel[Int](0).withExpiration(300 milliseconds, true) + ch.awrite(1).withTimeout(2 seconds).transform{ + case Failure(ex) => + assert(ex.isInstanceOf[TimeoutException]) + Success(()) + case Success(u) => + assert(""=="TimeoutException expected") + Failure(new RuntimeException()) + } + } + + + + test("expire must be an order") { + val ch = makeChannel[Int](10).withExpiration(300 milliseconds, false) + val fr1 = ch.aread + val fr2 = ch.aread + for { + _ <- ch.awriteAll(List(1,2)) + _ <- Time.asleep(10 milliseconds) + fr3 = ch.aread + r3 <- fr3.withTimeout(1 second).transform{ + case Failure(ex: TimeoutException) => + Success(()) + case other => + assert(""==s"TimeoutException expected, we have $other") + Failure(new RuntimeException()) + } + w4 <- ch.awrite(4) + r31 <- fr3 + } yield assert(r31 == 4) + } + + +} diff --git a/shared/src/test/scala/gopher/channels/FutureWithTimeout.scala b/shared/src/test/scala/gopher/channels/FutureWithTimeout.scala new file mode 100644 index 00000000..31de76bf --- /dev/null +++ b/shared/src/test/scala/gopher/channels/FutureWithTimeout.scala @@ -0,0 +1,22 @@ +package gopher.channels + +import gopher._ +import scala.concurrent.Future +import scala.concurrent.Promise +import scala.concurrent.ExecutionContext +import scala.concurrent.TimeoutException +import scala.concurrent.duration.FiniteDuration + + +extension [T](f: Future[T]) { + + def withTimeout(d: FiniteDuration)(using gopherApi: Gopher[Future], ec: ExecutionContext): Future[T] = + val p = Promise[T] + f.onComplete(p.tryComplete) + gopherApi.time.schedule({ () => + p.tryFailure(new TimeoutException()) + }, d) + p.future + +} + diff --git a/shared/src/test/scala/gopher/channels/SelectSuite.scala b/shared/src/test/scala/gopher/channels/SelectSuite.scala index a9f3bdb6..fc225a04 100644 --- a/shared/src/test/scala/gopher/channels/SelectSuite.scala +++ b/shared/src/test/scala/gopher/channels/SelectSuite.scala @@ -198,7 +198,6 @@ class SelectSuite extends FunSuite ch1s=ir true case iw: channel3.write if (iw==(x+1)) => - {}; nw = nw+1 true case t: Time.after if t == (5 milliseconds) => q=true From 3d8c322ec67c51b42d6a665fe5d39e36fa3e6587 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Mon, 4 Jan 2021 09:15:20 +0200 Subject: [PATCH 032/161] implemented map and flatMap over channels --- .../src/main/scala/gopher/ReadChannel.scala | 4 +- .../scala/gopher/{ => impl}/DuppedInput.scala | 1 + .../gopher/impl/FlatMappedReadChannel.scala | 47 +++++++++++++++++++ .../scala/gopher/impl/MappedChannel.scala | 13 +++++ .../scala/gopher/impl/MappedReadChannel.scala | 41 ++++++++++++++++ 5 files changed, 104 insertions(+), 2 deletions(-) rename shared/src/main/scala/gopher/{ => impl}/DuppedInput.scala (95%) create mode 100644 shared/src/main/scala/gopher/impl/FlatMappedReadChannel.scala create mode 100644 shared/src/main/scala/gopher/impl/MappedChannel.scala create mode 100644 shared/src/main/scala/gopher/impl/MappedReadChannel.scala diff --git a/shared/src/main/scala/gopher/ReadChannel.scala b/shared/src/main/scala/gopher/ReadChannel.scala index d1bd30a3..090fa8da 100644 --- a/shared/src/main/scala/gopher/ReadChannel.scala +++ b/shared/src/main/scala/gopher/ReadChannel.scala @@ -13,7 +13,7 @@ trait ReadChannel[F[_], A]: type read = A - protected def gopherApi: Gopher[F] + def gopherApi: Gopher[F] def asyncMonad: CpsSchedulingMonad[F] = gopherApi.asyncMonad @@ -105,7 +105,7 @@ trait ReadChannel[F[_], A]: def addDoneReader(reader: Reader[Unit]): Unit = thisReadChannel.addDoneReader(reader) - protected def gopherApi: Gopher[F] = thisReadChannel.gopherApi + def gopherApi: Gopher[F] = thisReadChannel.gopherApi end DoneReadChannel diff --git a/shared/src/main/scala/gopher/DuppedInput.scala b/shared/src/main/scala/gopher/impl/DuppedInput.scala similarity index 95% rename from shared/src/main/scala/gopher/DuppedInput.scala rename to shared/src/main/scala/gopher/impl/DuppedInput.scala index 67256cac..c69aaac0 100644 --- a/shared/src/main/scala/gopher/DuppedInput.scala +++ b/shared/src/main/scala/gopher/impl/DuppedInput.scala @@ -30,5 +30,6 @@ class DuppedInput[F[_],A](origin:ReadChannel[F,A], bufSize: Int=1)(using api:Gop sink2.close() false }.runAsync() + api.asyncMonad.spawn(runner) } diff --git a/shared/src/main/scala/gopher/impl/FlatMappedReadChannel.scala b/shared/src/main/scala/gopher/impl/FlatMappedReadChannel.scala new file mode 100644 index 00000000..664bb8d1 --- /dev/null +++ b/shared/src/main/scala/gopher/impl/FlatMappedReadChannel.scala @@ -0,0 +1,47 @@ +package gopher.impl + +import cps._ +import gopher._ +import scala.util._ + +class FlatMappedReadChannel[F[_],A, B](prev: ReadChannel[F,A], f: A=>ReadChannel[F,B]) extends ReadChannel[F,B] { + + def addReader(reader: Reader[B]): Unit = + bChannel.addReader(reader) + + + def addDoneReader(reader: Reader[Unit]): Unit = { + bChannel.addDoneReader(reader) + } + + def gopherApi:Gopher[F] = prev.gopherApi + + val bChannel = gopherApi.makeChannel[B]() + + def run(): F[Unit] = + given CpsSchedulingMonad[F] = gopherApi.asyncMonad + async[F]{ + while{ + prev.optRead match + case Some(a) => + val internal = f(a) + while{ + internal.optRead match + case Some(b) => + bChannel.write(b) + true + case None => + false + } do () + true + case None => + false + } do () + bChannel.close() + } + + gopherApi.asyncMonad.spawn(run()) + + + +} \ No newline at end of file diff --git a/shared/src/main/scala/gopher/impl/MappedChannel.scala b/shared/src/main/scala/gopher/impl/MappedChannel.scala new file mode 100644 index 00000000..e3682208 --- /dev/null +++ b/shared/src/main/scala/gopher/impl/MappedChannel.scala @@ -0,0 +1,13 @@ +package gopher.impl + +import gopher._ + +class MappedChannel[F[_],W,RA,RB](internal: Channel[F,W,RA], f: RA=>RB) extends MappedReadChannel[F,RA,RB](internal, f) + with Channel[F,W,RB]: + + override def addWriter(writer: Writer[W]): Unit = + internal.addWriter(writer) + + override def close(): Unit = + internal.close() + diff --git a/shared/src/main/scala/gopher/impl/MappedReadChannel.scala b/shared/src/main/scala/gopher/impl/MappedReadChannel.scala new file mode 100644 index 00000000..cfebfbfe --- /dev/null +++ b/shared/src/main/scala/gopher/impl/MappedReadChannel.scala @@ -0,0 +1,41 @@ +package gopher.impl + +import gopher._ +import scala.util._ + +class MappedReadChannel[F[_],A, B](internal: ReadChannel[F,A], f: A=>B) extends ReadChannel[F,B] { + + def addDoneReader(reader: Reader[Unit]): Unit = internal.addDoneReader(reader) + + class MReader(nested: Reader[B]) extends Reader[A] { + + def wrappedFun(fun: (Try[B] => Unit) ): (Try[A] => Unit) = { + case Success(a) => + val b = f(a) + fun(Success(b)) + case Failure(ex) => + fun(Failure(ex)) + } + + //TODO: think, are we want to pass error to the next level ? + override def capture(): Option[Try[A]=>Unit] = + nested.capture().map{ fun => + wrappedFun(fun) + } + + override def canExpire: Boolean = nested.canExpire + + override def isExpired: Boolean = nested.isExpired + + override def markUsed(): Unit = nested.markUsed() + + override def markFree(): Unit = nested.markFree() + + } + + def addReader(reader: Reader[B]): Unit = + internal.addReader(MReader(reader)) + + def gopherApi:Gopher[F] = internal.gopherApi + +} \ No newline at end of file From a59cb773993662990c07de2016bf502e2cde20b0 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Sun, 17 Jan 2021 13:52:57 +0200 Subject: [PATCH 033/161] porting MacroSelectSuit in progress --- .../IOComposeSuite.scala | 6 +- .../scala/gopher/impl/PromiseChannel.scala | 15 +- shared/src/main/scala/gopher/Channel.scala | 19 ++ shared/src/main/scala/gopher/Gopher.scala | 12 + .../src/main/scala/gopher/ReadChannel.scala | 1 + shared/src/main/scala/gopher/Select.scala | 183 +++++++++-- .../src/main/scala/gopher/SelectGroup.scala | 16 + shared/src/main/scala/gopher/SelectLoop.scala | 2 +- .../src/main/scala/gopher/WriteChannel.scala | 4 + .../gopher/impl/ChFlatMappedChannel.scala | 13 + ...el.scala => ChFlatMappedReadChannel.scala} | 2 +- .../gopher/channels/MacroSelectSuite.scala | 297 +++++++++--------- 12 files changed, 381 insertions(+), 189 deletions(-) rename 0.99.x/src/test/scala/gopher/{channels => obsolete}/IOComposeSuite.scala (93%) create mode 100644 shared/src/main/scala/gopher/impl/ChFlatMappedChannel.scala rename shared/src/main/scala/gopher/impl/{FlatMappedReadChannel.scala => ChFlatMappedReadChannel.scala} (88%) rename {0.99.x => shared}/src/test/scala/gopher/channels/MacroSelectSuite.scala (64%) diff --git a/0.99.x/src/test/scala/gopher/channels/IOComposeSuite.scala b/0.99.x/src/test/scala/gopher/obsolete/IOComposeSuite.scala similarity index 93% rename from 0.99.x/src/test/scala/gopher/channels/IOComposeSuite.scala rename to 0.99.x/src/test/scala/gopher/obsolete/IOComposeSuite.scala index 8de96ea4..569bd27f 100644 --- a/0.99.x/src/test/scala/gopher/channels/IOComposeSuite.scala +++ b/0.99.x/src/test/scala/gopher/obsolete/IOComposeSuite.scala @@ -1,9 +1,9 @@ package gopher.channels -import gopher.ChannelClosedException -import org.scalatest.AsyncFunSuite +import gopher._ +import munit._ -class IOComposeSuite extends AsyncFunSuite { +class IOComposeSuite extends FunSuite { test("simple composition of IO with map") { diff --git a/jvm/src/main/scala/gopher/impl/PromiseChannel.scala b/jvm/src/main/scala/gopher/impl/PromiseChannel.scala index ef4bf9ea..831f5627 100644 --- a/jvm/src/main/scala/gopher/impl/PromiseChannel.scala +++ b/jvm/src/main/scala/gopher/impl/PromiseChannel.scala @@ -24,19 +24,8 @@ import scala.util.Failure protected val readed: AtomicBoolean = new AtomicBoolean(false) def addReader(reader: Reader[A]): Unit = - if (ref.get() eq null) then - readers.add(reader) - step() - else - var done = false - reader.capture() match - case Some(f) => - reader.markUsed() - taskExecutor.execute(() => f(Failure(new ChannelClosedException()))) - done = true - case None => - readers.add(reader) - step() + readers.add(reader) + step() def addWriter(writer: Writer[A]): Unit = var done = false diff --git a/shared/src/main/scala/gopher/Channel.scala b/shared/src/main/scala/gopher/Channel.scala index 8bd9c562..2a4ba76d 100644 --- a/shared/src/main/scala/gopher/Channel.scala +++ b/shared/src/main/scala/gopher/Channel.scala @@ -4,6 +4,8 @@ import cps._ import java.io.Closeable import scala.concurrent.duration.FiniteDuration +import gopher.impl._ + trait Channel[F[_],W,R] extends WriteChannel[F,W] with ReadChannel[F,R] with Closeable: override def gopherApi: Gopher[F] @@ -11,6 +13,23 @@ trait Channel[F[_],W,R] extends WriteChannel[F,W] with ReadChannel[F,R] with Clo def withExpiration(ttl: FiniteDuration, throwTimeouts: Boolean): ChannelWithExpiration[F,W,R] = new ChannelWithExpiration(this, ttl, throwTimeouts) + def map[R1](f: R=>R1): Channel[F,W,R1] = + MappedChannel(this,f) + + def flatMap[R1](f: R=> ReadChannel[F,R1]): Channel[F,W,R1] = + ChFlatMappedChannel(this,f) + + +end Channel + +object Channel: + + case class Read[F[_],A](a:A, ch:ReadChannel[F,A]|F[A]) { + type Element = A + } + case class FRead[F[_],A](a:A, ch: F[A]) + case class Write[F[_],A](a: A, ch: WriteChannel[F,A]) + end Channel diff --git a/shared/src/main/scala/gopher/Gopher.scala b/shared/src/main/scala/gopher/Gopher.scala index aeb58a43..e13d83ea 100644 --- a/shared/src/main/scala/gopher/Gopher.scala +++ b/shared/src/main/scala/gopher/Gopher.scala @@ -2,6 +2,7 @@ package gopher import cps._ import scala.concurrent.duration.Duration +import scala.util.Try import java.util.logging.{Level => LogLevel} @@ -43,3 +44,14 @@ def makeOnceChannel[A]()(using g:Gopher[?]): Channel[g.Monad,A,A] = def select(using g:Gopher[?]):Select[g.Monad] = g.select +def futureInput[F[_],A](f: F[A])(using g: Gopher[F]): ReadChannel[F,A] = + val ch = g.makeOnceChannel[Try[A]]() + g.asyncMonad.spawn{ + g.asyncMonad.flatMapTry(f)(r => ch.awrite(r)) + } + ch.map(_.get) + +extension [F[_],A](fa: F[A])(using g: Gopher[F]) + def asChannel : ReadChannel[F,A] = + futureInput(fa) + diff --git a/shared/src/main/scala/gopher/ReadChannel.scala b/shared/src/main/scala/gopher/ReadChannel.scala index 090fa8da..cbdaf4ad 100644 --- a/shared/src/main/scala/gopher/ReadChannel.scala +++ b/shared/src/main/scala/gopher/ReadChannel.scala @@ -126,3 +126,4 @@ end ReadChannel + diff --git a/shared/src/main/scala/gopher/Select.scala b/shared/src/main/scala/gopher/Select.scala index 84b82dc8..468c7f73 100644 --- a/shared/src/main/scala/gopher/Select.scala +++ b/shared/src/main/scala/gopher/Select.scala @@ -11,7 +11,7 @@ class Select[F[_]:CpsSchedulingMonad](api: Gopher[F]): inline def apply[A](inline pf: PartialFunction[Any,A]): A = ${ - Select.onceImpl[F,A]('pf, '{summonInline[CpsSchedulingMonad[F]]}, 'api ) + Select.onceImpl[F,A]('pf, 'api ) } def group[S]: SelectGroup[F,S] = new SelectGroup[F,S](api) @@ -20,6 +20,64 @@ class Select[F[_]:CpsSchedulingMonad](api: Gopher[F]): def loop: SelectLoop[F] = new SelectLoop[F](api) + def fold[S](s0:S)(step: (S,SelectGroup[F,S|SelectFold.Done[S]])=> S | SelectFold.Done[S]): S = { + var s: S = s0 + while{ + val g = SelectGroup[F,S|SelectFold.Done[S]](api) + step(s,g) match { + case SelectFold.Done(r:S) => + s=r + false + case other => + s=other.asInstanceOf[S] + true + } + } do () + s + } + + def fold_async[S](s0:S)(step: (S,SelectGroup[F,S|SelectFold.Done[S]])=> F[S | SelectFold.Done[S]]): F[S] = { + var g = SelectGroup[F,S|SelectFold.Done[S]](api) + api.asyncMonad.flatMap(step(s0,g)){ s => + s match + case SelectFold.Done(r) => api.asyncMonad.pure(r.asInstanceOf[S]) + case other => fold_async[S](other.asInstanceOf[S])(step) + } + } + + inline def afold[S](s0:S)(inline step: (S,SelectGroup[F,S|SelectFold.Done[S]])=> S | SelectFold.Done[S]) : F[S] = + async[F](using api.asyncMonad).apply{ + fold(s0)(step) + } + + //def map[A](step: PartialFunction[SelectGroup[F,A],A|SelectFold.Done[Unit]]): ReadChannel[F,A] = + + def map[A](step: SelectGroup[F,A] => A|SelectFold.Done[Unit]): ReadChannel[F,A] = + mapAsync[A](x => api.asyncMonad.pure(step(x))) + + def mapAsync[A](step: SelectGroup[F,A] => F[A|SelectFold.Done[Unit]]): ReadChannel[F,A] = + val r = makeChannel[A]()(using api) + api.asyncMonad.spawn{ + async{ + var done = false + while(!done) { + val g = SelectGroup[F,A](api) + await(step(g)) match + case SelectFold.Done(()) => done=true + case other => + r.write(other.asInstanceOf[A]) + } + } + } + r + + def map_async[A](step: SelectGroup[F,A] => F[A|SelectFold.Done[Unit]]): F[ReadChannel[F,A]] = + api.asyncMonad.pure(mapAsync(step)) + + + + + object Select: @@ -46,10 +104,10 @@ object Select: def appended[L <: SelectListeners[F,S]: Type](base: Expr[L])(using Quotes): Expr[L] = '{ $base.onRead($ch.done)($f) } - def onceImpl[F[_]:Type, A:Type](pf: Expr[PartialFunction[Any,A]], m: Expr[CpsSchedulingMonad[F]], api: Expr[Gopher[F]])(using Quotes): Expr[A] = + def onceImpl[F[_]:Type, A:Type](pf: Expr[PartialFunction[Any,A]], api: Expr[Gopher[F]])(using Quotes): Expr[A] = def builder(caseDefs: List[SelectorCaseExpr[F,A]]):Expr[A] = { val s0 = '{ - new SelectGroup[F,A]($api)(using $m) + new SelectGroup[F,A]($api)(using ${api}.asyncMonad) } val g: Expr[SelectGroup[F,A]] = caseDefs.foldLeft(s0){(s,e) => e.appended(s) @@ -59,10 +117,10 @@ object Select: } runImpl( builder, pf) - def loopImpl[F[_]:Type](pf: Expr[PartialFunction[Any,Boolean]], m: Expr[CpsSchedulingMonad[F]], api: Expr[Gopher[F]])(using Quotes): Expr[Unit] = + def loopImpl[F[_]:Type](pf: Expr[PartialFunction[Any,Boolean]], api: Expr[Gopher[F]])(using Quotes): Expr[Unit] = def builder(caseDefs: List[SelectorCaseExpr[F,Boolean]]):Expr[Unit] = { val s0 = '{ - new SelectLoop[F]($api)(using $m) + new SelectLoop[F]($api)(using ${api}.asyncMonad) } val g: Expr[SelectLoop[F]] = caseDefs.foldLeft(s0){(s,e) => e.appended(s) @@ -99,29 +157,52 @@ object Select: def parseCaseDef[F[_]:Type,S:Type](using Quotes)(caseDef: quotes.reflect.CaseDef): SelectorCaseExpr[F,S] = import quotes.reflect._ + + val caseDefGuard = parseCaseDefGuard(caseDef) + + def handleRead(bind: Bind, valName: String, channel:Term, tp:TypeRepr): SelectorCaseExpr[F,S] = + val readFun = makeLambda(valName,tp,bind.symbol,caseDef.rhs) + if (channel.tpe <:< TypeRepr.of[ReadChannel[F,?]]) + tp.asType match + case '[a] => ReadExpression(channel.asExprOf[ReadChannel[F,a]],readFun.asExprOf[a=>S]) + case _ => + reportError("can't determinate read type", caseDef.pattern.asExpr) + else + reportError("read pattern is not a read channel", channel.asExpr) + + def handleWrite(bind: Bind, valName: String, channel:Term, tp:TypeRepr): SelectorCaseExpr[F,S] = + val writeFun = makeLambda(valName,tp, bind.symbol, caseDef.rhs) + val e = caseDefGuard.getOrElse(valName, + reportError(s"not found binding ${valName} in write condition", caseDef.pattern.asExpr) + ) + if (channel.tpe <:< TypeRepr.of[WriteChannel[F,?]]) then + tp.asType match + case '[a] => + WriteExpression(channel.asExprOf[WriteChannel[F,a]],e.asExprOf[a], writeFun.asExprOf[a=>S]) + case _ => + reportError("Can't determinate type of write", caseDef.pattern.asExpr) + else + reportError("Write channel expected", channel.asExpr) + + def extractType[F[_]:Type](name: "read"|"write", channelTerm: Term, pat: Tree): TypeRepr = + import quotes.reflect._ + pat match + case Typed(_,tp) => tp.tpe + case _ => + TypeSelect(channelTerm,name).tpe + + caseDef.pattern match case Inlined(_,List(),body) => parseCaseDef(CaseDef(body, caseDef.guard, caseDef.rhs)) case b@Bind(v, tp@Typed(expr, TypeSelect(ch,"read"))) => - val readFun = makeLambda(v,tp.tpe,b.symbol,caseDef.rhs) - if (ch.tpe <:< TypeRepr.of[ReadChannel[F,?]]) - tp.tpe.asType match - case '[a] => ReadExpression(ch.asExprOf[ReadChannel[F,a]],readFun.asExprOf[a=>S]) - case _ => - reportError("can't determinate read type", caseDef.pattern.asExpr) - else - reportError("read pattern is not a read channel", ch.asExpr) + handleRead(b,v,ch,tp.tpe) + case b@Bind(v, tp@Typed(expr, Annotated(TypeSelect(ch,"read"),_))) => + handleRead(b,v,ch,tp.tpe) case b@Bind(v, tp@Typed(expr, TypeSelect(ch,"write"))) => - val writeFun = makeLambda(v,tp.tpe, b.symbol, caseDef.rhs) - val e = matchCaseDefCondition(caseDef, v) - if (ch.tpe <:< TypeRepr.of[WriteChannel[F,?]]) then - tp.tpe.asType match - case '[a] => - WriteExpression(ch.asExprOf[WriteChannel[F,a]],e.asExprOf[a], writeFun.asExprOf[a=>S]) - case _ => - reportError("Can't determinate type of write", tp.asExpr) - else - reportError("Write channel expected", ch.asExpr) + handleWrite(b,v,ch,tp.tpe) + case b@Bind(v, tp@Typed(expr, Annotated(TypeSelect(ch,"write"),_))) => + handleWrite(b,v,ch,tp.tpe) case b@Bind(v, tp@Typed(expr, TypeSelect(ch,"after"))) => val timeoutFun = makeLambda(v, tp.tpe, b.symbol, caseDef.rhs) val e = matchCaseDefCondition(caseDef, v) @@ -139,8 +220,23 @@ object Select: reportError("done base is not a read channel", ch.asExpr) case _ => reportError("can't determinate read type", caseDef.pattern.asExpr) - - + case pat@Unapply(TypeApply(quotes.reflect.Select( + quotes.reflect.Select(chobj,nameReadOrWrite), + "unapply"),targs), + impl,List(b@Bind(e,ePat),Bind(ch,chPat))) => + if (chobj.tpe == '{gopher.Channel}.asTerm.tpe) + val chExpr = caseDefGuard.getOrElse(ch,reportError(s"select condition for ${ch} is not found",caseDef.pattern.asExpr)) + nameReadOrWrite match + case "Read" => + val elementType = extractType("read",chExpr, ePat) + handleRead(b,e,chExpr,elementType) + case "Write" => + val elementType = extractType("write",chExpr, ePat) + handleWrite(b,e,chExpr,elementType) + case _ => + reportError(s"Read or Write expected, we have ${nameReadOrWrite}", caseDef.pattern.asExpr) + else + reportError("Incorrect select pattern, expected or x:channel.{read,write} or Channel.{Read,Write}",chobj.asExpr) case _ => report.error( s""" @@ -155,6 +251,7 @@ object Select: end parseCaseDef + def matchCaseDefCondition(using Quotes)(caseDef: quotes.reflect.CaseDef, v: String): quotes.reflect.Term = import quotes.reflect._ caseDef.guard match @@ -164,13 +261,40 @@ object Select: if (v1 != v) { reportError(s"write name mismatch ${v1}, expected ${v}", condition.asExpr) } - // TODO: check that method is '=='' expr case _ => reportError(s"Condition is not in form x==expr,${condition} ",condition.asExpr) case _ => reportError(s"Condition is required ",caseDef.pattern.asExpr) - + + def parseCaseDefGuard(using Quotes)(caseDef: quotes.reflect.CaseDef): Map[String,quotes.reflect.Term] = + import quotes.reflect._ + caseDef.guard match + case Some(condition) => + parseSelectCondition(condition, Map.empty) + case None => + Map.empty + + + def parseSelectCondition(using Quotes)(condition: quotes.reflect.Term, + entries:Map[String,quotes.reflect.Term]): Map[String,quotes.reflect.Term] = + import quotes.reflect._ + condition match + case Apply(quotes.reflect.Select(Ident(v1),"=="),List(expr)) => + entries.updated(v1, expr) + case Apply(quotes.reflect.Select(frs, "&&" ), List(snd)) => + parseSelectCondition(snd, parseSelectCondition(frs, entries)) + case _ => + reportError( + s"""Invalid select guard form, expected one of + channelName == channelEpxr + writeBind == writeExpresion + condition && condition + we have + ${condition.show} + """, + condition.asExpr) + def makeLambda(using Quotes)(argName: String, argType: quotes.reflect.TypeRepr, @@ -194,13 +318,12 @@ object Select: case _ => super.transformTerm(tree)(owner) } argTransformer.transformTerm(term)(owner) + def reportError(message: String, posExpr: Expr[?])(using Quotes): Nothing = import quotes.reflect._ report.error(message, posExpr) throw new RuntimeException(s"Error in macro: $message") + - - - diff --git a/shared/src/main/scala/gopher/SelectGroup.scala b/shared/src/main/scala/gopher/SelectGroup.scala index 94fa4cd5..2dd6ea08 100644 --- a/shared/src/main/scala/gopher/SelectGroup.scala +++ b/shared/src/main/scala/gopher/SelectGroup.scala @@ -61,6 +61,22 @@ class SelectGroup[F[_]:CpsSchedulingMonad, S](api: Gopher[F]) extends SelectLis inline def run(): S = await(step()) + inline def apply(inline pf: PartialFunction[Any,S]): S = + ${ + Select.onceImpl[F,S]('pf, 'api ) + } + + inline def select(inline pf: PartialFunction[Any,S]): S = + ${ + Select.onceImpl[F,S]('pf, 'api ) + } + + /** + * short alias for SelectFold.Done + */ + def done[S](s:S):SelectFold.Done[S] = + SelectFold.Done(s) + /** * FluentDSL for user SelectGroup without macroses. *``` diff --git a/shared/src/main/scala/gopher/SelectLoop.scala b/shared/src/main/scala/gopher/SelectLoop.scala index 4f90c791..72c3f2eb 100644 --- a/shared/src/main/scala/gopher/SelectLoop.scala +++ b/shared/src/main/scala/gopher/SelectLoop.scala @@ -64,7 +64,7 @@ class SelectLoop[F[_]:CpsSchedulingMonad](api: Gopher[F]) extends SelectListener inline def apply(inline pf: PartialFunction[Any,Boolean]): Unit = ${ - Select.loopImpl[F]('pf, '{summonInline[CpsSchedulingMonad[F]]}, 'api ) + Select.loopImpl[F]('pf, 'api ) } diff --git a/shared/src/main/scala/gopher/WriteChannel.scala b/shared/src/main/scala/gopher/WriteChannel.scala index 56f703e4..6f494d8d 100644 --- a/shared/src/main/scala/gopher/WriteChannel.scala +++ b/shared/src/main/scala/gopher/WriteChannel.scala @@ -27,6 +27,10 @@ trait WriteChannel[F[_], A]: @targetName("write1") inline def <~ (a:A): Unit = await(awrite(a))(using asyncMonad) + @targetName("write2") + inline def ! (a:A): Unit = await(awrite(a))(using asyncMonad) + + //def Write(x:A):WritePattern = new WritePattern(x) //class WritePattern(x:A): diff --git a/shared/src/main/scala/gopher/impl/ChFlatMappedChannel.scala b/shared/src/main/scala/gopher/impl/ChFlatMappedChannel.scala new file mode 100644 index 00000000..7f37ed21 --- /dev/null +++ b/shared/src/main/scala/gopher/impl/ChFlatMappedChannel.scala @@ -0,0 +1,13 @@ +package gopher.impl + +import gopher._ + +class ChFlatMappedChannel[F[_],W,RA,RB](internal: Channel[F,W,RA], f: RA=>ReadChannel[F,RB]) extends ChFlatMappedReadChannel[F,RA,RB](internal, f) + with Channel[F,W,RB]: + + override def addWriter(writer: Writer[W]): Unit = + internal.addWriter(writer) + + override def close(): Unit = + internal.close() + diff --git a/shared/src/main/scala/gopher/impl/FlatMappedReadChannel.scala b/shared/src/main/scala/gopher/impl/ChFlatMappedReadChannel.scala similarity index 88% rename from shared/src/main/scala/gopher/impl/FlatMappedReadChannel.scala rename to shared/src/main/scala/gopher/impl/ChFlatMappedReadChannel.scala index 664bb8d1..ce42534c 100644 --- a/shared/src/main/scala/gopher/impl/FlatMappedReadChannel.scala +++ b/shared/src/main/scala/gopher/impl/ChFlatMappedReadChannel.scala @@ -4,7 +4,7 @@ import cps._ import gopher._ import scala.util._ -class FlatMappedReadChannel[F[_],A, B](prev: ReadChannel[F,A], f: A=>ReadChannel[F,B]) extends ReadChannel[F,B] { +class ChFlatMappedReadChannel[F[_], A, B](prev: ReadChannel[F,A], f: A=>ReadChannel[F,B]) extends ReadChannel[F,B] { def addReader(reader: Reader[B]): Unit = bChannel.addReader(reader) diff --git a/0.99.x/src/test/scala/gopher/channels/MacroSelectSuite.scala b/shared/src/test/scala/gopher/channels/MacroSelectSuite.scala similarity index 64% rename from 0.99.x/src/test/scala/gopher/channels/MacroSelectSuite.scala rename to shared/src/test/scala/gopher/channels/MacroSelectSuite.scala index adbb0c5d..0f79e6ec 100644 --- a/0.99.x/src/test/scala/gopher/channels/MacroSelectSuite.scala +++ b/shared/src/test/scala/gopher/channels/MacroSelectSuite.scala @@ -1,42 +1,45 @@ package gopher.channels +import cps._ import gopher._ -import org.scalatest._ +import munit._ import scala.concurrent.{Channel=>_,_} import scala.concurrent.duration._ -import scala.language._ +import scala.language.postfixOps -class MacroSelectASyncSuite extends AsyncFunSuite +import cps.monads.FutureAsyncMonad + +class MacroSelectSuite extends FunSuite { - override implicit def executionContext: ExecutionContext = ExecutionContext.Implicits.global + import ExecutionContext.Implicits.global + given Gopher[Future] = SharedGopherAPI.apply[Future]() + test("select emulation with macroses") { - val channel = gopherApi.makeChannel[Int](100) + val channel = makeChannel[Int](100) - go { + async[Future] { var i = 1 while(i <= 1000) { channel <~ i i+=1 } //TODO: implement for in goas preprocessor to async + // dotty bug: position not set //for( i <- 1 to 1000) // channel <~ i } var sum = 0 - val consumer = go { - for(s <- gopherApi.select.forever) { - s match { - case i: channel.read => - //System.err.println("received:"+i) - sum = sum + i - if (i==1000) - implicitly[FlowTermination[Unit]].doExit(()) - } + val consumer = async[Future] { + select.loop{ + case i: channel.read => + //System.err.println("received:"+i) + sum = sum + i + i < 1000 } sum } @@ -48,29 +51,27 @@ class MacroSelectASyncSuite extends AsyncFunSuite } + test("select with run-once") { - import gopherApi._ val channel1 = makeChannel[Int](100) val channel2 = makeChannel[Int](100) - val g = go { + val g = async[Future] { var nWrites=0 - for(s <- select.once) - s match { - case x: channel1.write if (x==1) => { - {}; nWrites = nWrites + 1 + select{ + case x: channel1.write if (x==1) => { + nWrites = nWrites + 1 } - case x: channel2.write if (x==1) => { - {}; nWrites = nWrites + 1 + case x: channel2.write if (x==1) => { + nWrites = nWrites + 1 } - } + } var nReads=0 - for(s <- select.once) - s match { + select { case x: channel1.read => { {}; nReads = nReads + 1 } case x: channel2.read => { {}; nReads = nReads + 1 } - } + } (nWrites, nReads) } @@ -79,108 +80,125 @@ class MacroSelectASyncSuite extends AsyncFunSuite } + test("select from futureInput") { - import gopherApi._ - val channel = makeChannel[Int](100) - val future = Future successful 10 - val fu = futureInput(future) - var res = 0 - val r = select.forever { - case x: channel.read => - Console.println(s"readed from channel: ${x}") - case x: fu.read => - //Console.println(s"readed from future: ${x}") - res = x - implicitly[FlowTermination[Unit]].doExit(()) - // syntax for using channels/futures in cases without - // setting one in stable identifers. - case x: Int if (x == future.read) => {}; - res = x + async[Future] { + val channel = makeChannel[Int](100) + val future = Future successful 10 + val fu = futureInput(future) + var res = 0 + val r = select{ + case x: channel.read => + Console.println(s"readed from channel: ${x}") + true + case x: fu.read => + //Console.println(s"readed from future: ${x}") + res = x + false + // syntax for using channels/futures in cases without + // setting one in stable identifers. + //case x: Int if (x == future.read) => + // res = x + } + assert(res == 10) } - r map (_ => assert(res == 10)) } + + /* + TODO: think, are we want to keep this syntax in 2.0.0 (?) test("select syntax with read/writes in guard") { import gopherApi._ val channel1 = makeChannel[Int](100) val channel2 = makeChannel[Int](100) var res = 0 - val r = select.forever{ + val r = select.loop{ case x: Int if (x==channel1.write(3)) => Console.println(s"write to channel1: ${x} ") + true case x: Int if (x==channel2.read) => Console.println(s"readed from channel2: ${x}") + true case x: Int if (x==(Future successful 10).read) => res=x - implicitly[FlowTermination[Unit]].doExit(()) + false } r map (_ => assert(res==10)) } + */ + test("select syntax with @unchecked annotation") { - import gopherApi._ val channel1 = makeChannel[List[Int]](100) val channel2 = makeChannel[List[Int]](100) var res = 0 - val r = select.once{ - case x: channel1.read @ unchecked => - {}; - res=1 - case x: List[Int] @ unchecked if (x==channel2.read) => - {}; - res=2 - } channel1.awrite(List(1,2,3)) - r map (_ => assert(res==1)) + async { + select.once{ + case x: channel1.read @ unchecked => + res=1 + case x: channel2.read @ unchecked => + res=2 + } + assert(res==1) + } + } + test("tuple in caseDef as one symbol") { - import gopherApi._ - val ch = makeChannel[(Int,Int)](100) - var res = 0 - val r = select.once{ - case xpair: ch.read @unchecked => - // fixed error in compiler: Can't find proxy - val (a,b)=xpair - res=1 + async { + val ch = makeChannel[(Int,Int)](100) + var res = 0 + ch.awrite((1,1)) + val r = select.once{ + case xpair: ch.read @unchecked => + // fixed error in compiler: Can't find proxy + val (a,b)=xpair + res=a + } + assert(res == 1) } - ch.awrite((1,1)) - r map ( _ => assert(res==1)) } + test("multiple readers for one write") { - import gopherApi._ val ch = makeChannel[Int](10) var x1 = 0 var x2 = 0 var x3 = 0 var x4 = 0 var x5 = 0 - val f1 = select.once{ - case x:ch.read => - {}; - x1=1 + val f1 = async { + select.once{ + case x:ch.read => + x1=1 + } } - val f2 = select.once{ - case x:ch.read => - {}; - x2=1 + val f2 = async { + select.once{ + case x:ch.read => + x2=1 + } } - val f3 = select.once{ - case x:ch.read => - {}; - x3=1 + val f3 = async { + select.once{ + case x:ch.read => + x3=1 + } } - val f4 = select.once{ - case x:ch.read => - {}; - x4=1 + val f4 = async { + select.once{ + case x:ch.read => + x4=1 + } } - val f5 = select.once{ - case x:ch.read => - {}; - x5=1 + val f5 = async{ + select.once{ + case x:ch.read => + x5=1 + } } for {_ <- ch.awrite(1) _ <- Future.firstCompletedOf(List(f1, f2, f3, f4, f5)) @@ -191,19 +209,20 @@ class MacroSelectASyncSuite extends AsyncFunSuite } yield assert(x1+x2+x3+x4+x5==1) } - + test("fold over selector") { - import gopherApi._ - val s0 = Seq[Future[Assertion]]() - val assertions = (1 to 100).foldLeft(s0) { (s, e) => val ch = makeChannel[Int](10) val back = makeChannel[Int]() val quit = Promise[Boolean]() - val r = select.afold(0){ (x,s) => - s match { - case a:ch.read => back <~ a - x+a - case q:Boolean if (q==quit.future.read) => CurrentFlowTermination.exit(x) + val quitChannel = futureInput(quit.future) + val r = async { + select.fold(0){ (x,g) => + g.select { + case a:ch.read => back <~ a + x+a + case q: quitChannel.read => + g.done(x) + } } } ch.awriteAll(1 to 10) @@ -212,25 +231,24 @@ class MacroSelectASyncSuite extends AsyncFunSuite quit success true } } - val fs = r map (sum => assert(sum==(1 to 10).sum)) - fs +: s - } - Future.sequence(assertions).map(_.head) + r map (sum => assert(sum==(1 to 10).sum)) } + test("fold over selector with idle-1") { - import gopherApi._ val ch1 = makeChannel[Int](10) val ch2 = makeChannel[Int](10) ch1.awrite(1) + //implicit val printCode = cps.macroFlags.PrintCode + //implicit val debugLevel = cps.macroFlags.DebugLevel(20) for { _ <- Future.successful(()) sf = select.afold((0, 0, 0)) { case ((n1, n2, nIdle), s) => - s match { + s.select{ case x: ch1.read => val nn1 = n1 + 1 if (nn1 > 100) { - CurrentFlowTermination.exit((nn1, n2, nIdle)) + s.done((nn1, n2, nIdle)) } else { ch2.write(x) (nn1, n2, nIdle) @@ -238,21 +256,20 @@ class MacroSelectASyncSuite extends AsyncFunSuite case x: ch2.read => ch1.write(x) (n1, n2 + 1, nIdle) - case _ => + case t : Time.after if (t == (50 milliseconds)) => (n1, n2, nIdle + 1) - } - } + } (n1, n2, ni) <- sf _ = assert(n1 + n2 + ni > 100) sf2 = select.afold((0, 0)) { case ((n1, nIdle), s) => - s match { + s.select{ case x: ch1.read => (n1 + 1, nIdle) - case _ => + case t: Time.after if t == (50 milliseconds) => val nni = nIdle + 1 if (nni > 3) { - CurrentFlowTermination.exit((n1, nni)) + s.done((n1, nni)) } else { (n1, nni) } @@ -264,42 +281,39 @@ class MacroSelectASyncSuite extends AsyncFunSuite } - lazy val gopherApi = CommonTestObjects.gopherApi -} - - -class MacroSelectSyncSuite extends FunSuite -{ - - import scala.concurrent.ExecutionContext.Implicits.global - - - - test("amap over selector") { - import gopherApi._ - val ch1 = makeChannel[Int](10) - val ch2 = makeChannel[Int](10) - val quit = Promise[Boolean]() - val out = select.amap { - case x:ch1.read => x*2 - case x:ch2.read => + /* + test("map over selector") { + val ch1 = makeChannel[Int](10) + val ch2 = makeChannel[Int](10) + val quit = Promise[Boolean]() + val out = select.map{ s => + s.apply{ + case x:ch1.read => x*2 + case Channel.Read(x:Int,ch) if ch == ch2 => //System.err.println(s"received:${x}") x*3 - case q:Boolean if (q==quit.future.read) => + case Channel.Read(q, ch) if ch == quit.future.asChannel => //System.err.println("received quit") - select.exit(1) - } - ch1.awriteAll(1 to 10) - ch2.awriteAll(100 to 110) - val f = out.afold(0){ - case (s,x) => //System.err.println(s"in afold ${x}") - s+x } - Thread.sleep(1000) - quit success true - val x = Await.result(f, 10 seconds) - assert(x > 3000) - } + s.done(()) + } + } + ch1.awriteAll(1 to 10) + ch2.awriteAll(100 to 110) + val f = async { + out.fold(0){ (s,x) => //System.err.println(s"in afold ${x}") + s+x + } + } + async { + Time.asleep(1 second) + quit success true + val x = await(f) + assert(x > 3000) + } + } + */ + /* test("generic channel make") { val ch1 = gopherApi.make[Channel[Int]]() val ch2 = gopherApi.make[Channel[Int]](1) @@ -442,5 +456,6 @@ class MacroSelectSyncSuite extends FunSuite lazy val gopherApi = CommonTestObjects.gopherApi + */ } From 6c845956910aa2babc8f63bfcae7fe94a3a56b03 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Sun, 24 Jan 2021 09:30:30 +0200 Subject: [PATCH 034/161] all MacroSelectSuite tests are ported to scala-3 + dotty-cps-asyn --- project/plugins.sbt | 2 +- shared/src/main/scala/gopher/Channel.scala | 4 +- .../src/main/scala/gopher/ReadChannel.scala | 29 +- shared/src/main/scala/gopher/Select.scala | 45 ++- shared/src/main/scala/gopher/SelectFold.scala | 6 + .../src/main/scala/gopher/WriteChannel.scala | 3 +- .../scala/gopher/impl/MappedChannel.scala | 9 + .../scala/gopher/impl/MappedReadChannel.scala | 52 +++- .../gopher/channels/MacroSelectSuite.scala | 280 +++++++++--------- 9 files changed, 250 insertions(+), 180 deletions(-) create mode 100644 shared/src/main/scala/gopher/SelectFold.scala diff --git a/project/plugins.sbt b/project/plugins.sbt index 02c9a3c1..c04c0de9 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -3,5 +3,5 @@ addSbtPlugin("com.jsuereth" % "sbt-pgp" % "2.0.2") addSbtPlugin("com.typesafe.sbt" % "sbt-site" % "1.4.0") addSbtPlugin("com.typesafe.sbt" % "sbt-ghpages" % "0.6.3") addSbtPlugin("org.portable-scala" % "sbt-scalajs-crossproject" % "1.0.0") -addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.3.1") +addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.4.0") diff --git a/shared/src/main/scala/gopher/Channel.scala b/shared/src/main/scala/gopher/Channel.scala index 2a4ba76d..6cd1fd17 100644 --- a/shared/src/main/scala/gopher/Channel.scala +++ b/shared/src/main/scala/gopher/Channel.scala @@ -13,9 +13,11 @@ trait Channel[F[_],W,R] extends WriteChannel[F,W] with ReadChannel[F,R] with Clo def withExpiration(ttl: FiniteDuration, throwTimeouts: Boolean): ChannelWithExpiration[F,W,R] = new ChannelWithExpiration(this, ttl, throwTimeouts) - def map[R1](f: R=>R1): Channel[F,W,R1] = + override def map[R1](f: R=>R1): Channel[F,W,R1] = MappedChannel(this,f) + //override def mapAsync[R1](f: R=>F[R1]): Channel[F,W,R1] = ??? + def flatMap[R1](f: R=> ReadChannel[F,R1]): Channel[F,W,R1] = ChFlatMappedChannel(this,f) diff --git a/shared/src/main/scala/gopher/ReadChannel.scala b/shared/src/main/scala/gopher/ReadChannel.scala index cbdaf4ad..a80e4d71 100644 --- a/shared/src/main/scala/gopher/ReadChannel.scala +++ b/shared/src/main/scala/gopher/ReadChannel.scala @@ -93,10 +93,37 @@ trait ReadChannel[F[_], A]: **/ inline def foreach(f: A=>Unit): Unit = await(aforeach(f))(using rAsyncMonad) - + + def map[B](f: A=>B): ReadChannel[F,B] = + new MappedReadChannel(this, f) + + def mapAsync[B](f: A=>F[B]): ReadChannel[F,B] = + new MappedAsyncReadChannel(this, f) + def dup(bufSize: Int=1, expiration: Duration=Duration.Inf): (ReadChannel[F,A], ReadChannel[F,A]) = DuppedInput(this, bufSize)(using gopherApi).pair + def afold[S](s0:S)(f: (S,A)=>S): F[S] = + fold_async(s0)((s,e) => asyncMonad.pure(f(s,e))) + + def fold_async[S](s0:S)(f: (S,A) => F[S] ): F[S] = + given CpsSchedulingMonad[F] = asyncMonad + async[F] { + var s = s0 + while{ + optRead match + case Some(a) => + s = await(f(s,a)) + true + case None => + false + }do() + s + } + + inline def fold[S](s0:S)(f: (S,A) => S ): S = + await[F,S](afold(s0)(f))(using rAsyncMonad) + class DoneReadChannel extends ReadChannel[F,Unit]: def addReader(reader: Reader[Unit]): Unit = diff --git a/shared/src/main/scala/gopher/Select.scala b/shared/src/main/scala/gopher/Select.scala index 468c7f73..d965d99a 100644 --- a/shared/src/main/scala/gopher/Select.scala +++ b/shared/src/main/scala/gopher/Select.scala @@ -52,28 +52,28 @@ class Select[F[_]:CpsSchedulingMonad](api: Gopher[F]): //def map[A](step: PartialFunction[SelectGroup[F,A],A|SelectFold.Done[Unit]]): ReadChannel[F,A] = - def map[A](step: SelectGroup[F,A] => A|SelectFold.Done[Unit]): ReadChannel[F,A] = + def map[A](step: SelectGroup[F,A] => A): ReadChannel[F,A] = mapAsync[A](x => api.asyncMonad.pure(step(x))) - def mapAsync[A](step: SelectGroup[F,A] => F[A|SelectFold.Done[Unit]]): ReadChannel[F,A] = + def mapAsync[A](step: SelectGroup[F,A] => F[A]): ReadChannel[F,A] = val r = makeChannel[A]()(using api) api.asyncMonad.spawn{ async{ var done = false - while(!done) { + while(!done) val g = SelectGroup[F,A](api) - await(step(g)) match - case SelectFold.Done(()) => done=true - case other => - r.write(other.asInstanceOf[A]) - } + try + val e = await(step(g)) + r.write(e) + catch + case ex: ChannelClosedException => + r.close() + done=true } } r - def map_async[A](step: SelectGroup[F,A] => F[A|SelectFold.Done[Unit]]): F[ReadChannel[F,A]] = - api.asyncMonad.pure(mapAsync(step)) - + @@ -199,13 +199,16 @@ object Select: handleRead(b,v,ch,tp.tpe) case b@Bind(v, tp@Typed(expr, Annotated(TypeSelect(ch,"read"),_))) => handleRead(b,v,ch,tp.tpe) + case tp@Typed(expr, TypeSelect(ch,"read")) => + // todo: introduce 'dummy' val + reportError("binding var in read expression is mandatory", caseDef.pattern.asExpr) case b@Bind(v, tp@Typed(expr, TypeSelect(ch,"write"))) => handleWrite(b,v,ch,tp.tpe) case b@Bind(v, tp@Typed(expr, Annotated(TypeSelect(ch,"write"),_))) => handleWrite(b,v,ch,tp.tpe) case b@Bind(v, tp@Typed(expr, TypeSelect(ch,"after"))) => val timeoutFun = makeLambda(v, tp.tpe, b.symbol, caseDef.rhs) - val e = matchCaseDefCondition(caseDef, v) + val e = caseDefGuard.getOrElse(v, reportError(s"can't find condifion for $v",caseDef.pattern.asExpr)) if (ch.tpe <:< TypeRepr.of[gopher.Time] || ch.tpe <:< TypeRepr.of[gopher.Time.type]) TimeoutExpression(e.asExprOf[FiniteDuration], timeoutFun.asExprOf[FiniteDuration => S]) else @@ -246,27 +249,13 @@ object Select: v: Time.after if v == expr we have ${caseDef.pattern.show} + (tree: ${caseDef.pattern}) """, caseDef.pattern.asExpr) reportError(s"unparsed caseDef pattern: ${caseDef.pattern}", caseDef.pattern.asExpr) end parseCaseDef - - def matchCaseDefCondition(using Quotes)(caseDef: quotes.reflect.CaseDef, v: String): quotes.reflect.Term = - import quotes.reflect._ - caseDef.guard match - case Some(condition) => - condition match - case Apply(quotes.reflect.Select(Ident(v1),method),List(expr)) => - if (v1 != v) { - reportError(s"write name mismatch ${v1}, expected ${v}", condition.asExpr) - } - expr - case _ => - reportError(s"Condition is not in form x==expr,${condition} ",condition.asExpr) - case _ => - reportError(s"Condition is required ",caseDef.pattern.asExpr) - + def parseCaseDefGuard(using Quotes)(caseDef: quotes.reflect.CaseDef): Map[String,quotes.reflect.Term] = import quotes.reflect._ caseDef.guard match diff --git a/shared/src/main/scala/gopher/SelectFold.scala b/shared/src/main/scala/gopher/SelectFold.scala new file mode 100644 index 00000000..7dd2c63e --- /dev/null +++ b/shared/src/main/scala/gopher/SelectFold.scala @@ -0,0 +1,6 @@ +package gopher + +object SelectFold: + + case class Done[S](s: S) + \ No newline at end of file diff --git a/shared/src/main/scala/gopher/WriteChannel.scala b/shared/src/main/scala/gopher/WriteChannel.scala index 6f494d8d..09e53a81 100644 --- a/shared/src/main/scala/gopher/WriteChannel.scala +++ b/shared/src/main/scala/gopher/WriteChannel.scala @@ -45,7 +45,8 @@ trait WriteChannel[F[_], A]: async[F]{ val it = collection.iterator while(it.hasNext) { - write(it.next()) + val v = it.next() + write(v) } } diff --git a/shared/src/main/scala/gopher/impl/MappedChannel.scala b/shared/src/main/scala/gopher/impl/MappedChannel.scala index e3682208..ce58dbb6 100644 --- a/shared/src/main/scala/gopher/impl/MappedChannel.scala +++ b/shared/src/main/scala/gopher/impl/MappedChannel.scala @@ -11,3 +11,12 @@ class MappedChannel[F[_],W,RA,RB](internal: Channel[F,W,RA], f: RA=>RB) extends override def close(): Unit = internal.close() + +class MappedAsyncChannel[F[_],W,RA,RB](internal: Channel[F,W,RA], f: RA=>F[RB]) extends MappedAsyncReadChannel[F,RA,RB](internal, f) + with Channel[F,W,RB]: + + override def addWriter(writer: Writer[W]): Unit = + internal.addWriter(writer) + + override def close(): Unit = + internal.close() diff --git a/shared/src/main/scala/gopher/impl/MappedReadChannel.scala b/shared/src/main/scala/gopher/impl/MappedReadChannel.scala index cfebfbfe..b019fae6 100644 --- a/shared/src/main/scala/gopher/impl/MappedReadChannel.scala +++ b/shared/src/main/scala/gopher/impl/MappedReadChannel.scala @@ -2,8 +2,9 @@ package gopher.impl import gopher._ import scala.util._ +import scala.util.control.NonFatal -class MappedReadChannel[F[_],A, B](internal: ReadChannel[F,A], f: A=>B) extends ReadChannel[F,B] { +class MappedReadChannel[F[_],A, B](internal: ReadChannel[F,A], f: A=> B) extends ReadChannel[F,B] { def addDoneReader(reader: Reader[Unit]): Unit = internal.addDoneReader(reader) @@ -38,4 +39,51 @@ class MappedReadChannel[F[_],A, B](internal: ReadChannel[F,A], f: A=>B) extends def gopherApi:Gopher[F] = internal.gopherApi -} \ No newline at end of file +} + +class MappedAsyncReadChannel[F[_],A, B](internal: ReadChannel[F,A], f: A=> F[B]) extends ReadChannel[F,B] { + + def addDoneReader(reader: Reader[Unit]): Unit = internal.addDoneReader(reader) + + class MReader(nested: Reader[B]) extends Reader[A] { + + def wrappedFun(fun: (Try[B] => Unit) ): (Try[A] => Unit) = { + case Success(a) => + try{ + asyncMonad.spawn( + asyncMonad.mapTry(f(a))(fun) + ) + }catch{ + case NonFatal(ex) => + fun(Failure(ex)) + } + case Failure(ex) => + fun(Failure(ex)) + } + + //TODO: think, are we want to pass error to the next level ? + override def capture(): Option[Try[A]=>Unit] = + nested.capture().map{ fun => + wrappedFun(fun) + } + + override def canExpire: Boolean = nested.canExpire + + override def isExpired: Boolean = nested.isExpired + + override def markUsed(): Unit = nested.markUsed() + + override def markFree(): Unit = nested.markFree() + + } + + def addReader(reader: Reader[B]): Unit = + internal.addReader(MReader(reader)) + + def gopherApi:Gopher[F] = internal.gopherApi + +} + + + + diff --git a/shared/src/test/scala/gopher/channels/MacroSelectSuite.scala b/shared/src/test/scala/gopher/channels/MacroSelectSuite.scala index 0f79e6ec..836c9fad 100644 --- a/shared/src/test/scala/gopher/channels/MacroSelectSuite.scala +++ b/shared/src/test/scala/gopher/channels/MacroSelectSuite.scala @@ -6,6 +6,7 @@ import munit._ import scala.concurrent.{Channel=>_,_} import scala.concurrent.duration._ +import scala.util._ import scala.language.postfixOps import cps.monads.FutureAsyncMonad @@ -280,182 +281,169 @@ class MacroSelectSuite extends FunSuite assert(n2i>3) } - - /* - test("map over selector") { + + test("map over selector".only) { val ch1 = makeChannel[Int](10) val ch2 = makeChannel[Int](10) val quit = Promise[Boolean]() - val out = select.map{ s => - s.apply{ - case x:ch1.read => x*2 - case Channel.Read(x:Int,ch) if ch == ch2 => - //System.err.println(s"received:${x}") + val quitChannel = quit.future.asChannel + val out = select.mapAsync[Int]{ s => + val v = async{ + s.apply{ + case x:ch1.read => + x*2 + case Channel.Read(x:Int,ch) if ch == ch2 => x*3 - case Channel.Read(q, ch) if ch == quit.future.asChannel => - //System.err.println("received quit") - s.done(()) + case Channel.Read(q, ch) if ch == quitChannel => + throw ChannelClosedException() + } } + v } ch1.awriteAll(1 to 10) ch2.awriteAll(100 to 110) - val f = async { - out.fold(0){ (s,x) => //System.err.println(s"in afold ${x}") - s+x - } - } + val f: Future[Int] = out.afold(0){ (s,x) => s+x } async { - Time.asleep(1 second) + Time.sleep(1 second) quit success true val x = await(f) + //println(s"x==$x") assert(x > 3000) } } - */ + + - /* - test("generic channel make") { - val ch1 = gopherApi.make[Channel[Int]]() - val ch2 = gopherApi.make[Channel[Int]](1) - // yet not supported by compiler. - //val ch3 = gopherApi.make[Channel[Int]](capacity=3) - val f1 = ch1.awrite(1) - val f2 = ch2.awrite(2) - val x = Await.result(ch1.aread, 10 seconds) - assert(x==1) - } - test("input afold") { - import gopherApi._ + test("input fold") { + val ch1 = makeChannel[Int]() + ch1.awriteAll(1 to 10) map { _ => ch1.close() } + async { + val x = ch1.fold(0){ case (s,x) => s+x } + assert(x==55) + } + } + + + test("map over selector") { val ch1 = makeChannel[Int]() - ch1.awriteAll(1 to 10) map { _ => ch1.close() } - val f = ch1.afold(0){ case (s,x) => s+x } - val x = Await.result(f, 10 seconds) - assert(x==55) - } - - test("map over selector") { - import gopherApi._ - val ch1 = gopherApi.make[Channel[Int]]() - val ch2 = gopherApi.make[Channel[Int]](1) + val ch2 = makeChannel[Int](1) val f1 = ch1.awrite(1) val f2 = ch2.awrite(2) - val chs = for(s <- select) yield { - s match { + async { + val chs = for(s <- select) yield { + s.apply{ case x:ch1.read => x*3 case x:ch2.read => x*5 } } - val fs1 = chs.aread - val fs2 = chs.aread - val s1 = Await.result(fs1, 1 second) - val s2 = Await.result(fs2, 1 second) - assert(s1==3 || s1==10) - } - - test("one-time channel make") { - val ch = gopherApi.make[OneTimeChannel[Int]]() - val f1 = ch.awrite(1) - val f2 = ch.awrite(2) - val x = Await.result(ch.aread, 10 seconds) - val x2 = Await.result(f2.failed, 10 seconds) - assert(x==1) - assert(x2.isInstanceOf[ChannelClosedException]) - } - - test("check for done signal from one-time channel") { - import gopherApi._ - val ch = gopherApi.make[OneTimeChannel[Int]]() - val sf = select.afold((0)){ (x,s) => - s match { - case v: ch.read => x + v - case _: ch.done => select.exit(x) - } + val fs1 = chs.aread + val fs2 = chs.aread + val s1 = await(fs1) + val s2 = await(fs2) + assert(s1==3 || s1==10) } - val f1 = ch.awrite(1) - val r = Await.result(sf,1 second) - assert(r==1) - } - - test("check for done signal from unbuffered channel") { - import gopherApi._ - val ch = gopherApi.make[Channel[Int]]() - val sf = select.afold((0)){ (x,s) => - s match { - case v: ch.closeless.read => x + v - case _: ch.done => select.exit(x) + } + + + + test("one-time channel make") { + val ch = makeOnceChannel[Int]() + val f1 = ch.awrite(1) + val f2 = ch.awrite(2) + async { + val x = await(ch.aread) + val x2 = Try(await(f2.failed)) + assert(x == 1) + assert(x2.get.isInstanceOf[ChannelClosedException]) } - } - val f1 = ch.awriteAll(1 to 5) map (_ =>ch.close) - val r = Await.result(sf,1 second) - assert(r==15) - } - - test("check for done signal from buffered channel") { - import gopherApi._ - val ch = gopherApi.make[Channel[Int]](10) - val sf = select.afold((0)){ (x,s) => - s match { - case v: ch.closeless.read => x + v - case _: ch.done => select.exit(x) + } + + + + test("check for done signal from one-time channel") { + val ch = makeOnceChannel[Int]() + val sf = select.afold((0)){ (x,s) => + s.select{ + case v: ch.read => x + v + case v: ch.done.read => s.done(x) + } } - } - val f1 = ch.awriteAll(1 to 5) map{ _ => - // let give all buffers to processe - Thread.sleep(200) - ch.close - } - val r = Await.result(sf,1 second) - assert(r==15) - } - - test("check for done signal from channel with dummy var") { - import gopherApi._ - val ch = gopherApi.make[Channel[Int]]() - val sf = select.afold((0)){ (x,s) => - s match { - case v: ch.closeless.read => x + v - case v: ch.done => select.exit(x) + val f1 = ch.awrite(1) + async { + val r = await(sf) + assert(r==1) } - } - val f1 = ch.awriteAll(1 to 5) map (_ =>ch.close) - val r = Await.result(sf,1 second) - assert(r==15) - } - - - test("check for done signal from select map") { - import gopherApi._ - val ch1 = gopherApi.make[Channel[Int]]() - val ch2 = gopherApi.make[Channel[Int]]() - val q = gopherApi.make[Channel[Boolean]]() - val chs = for(s <- select) yield { - s match { - case x: ch1.read => x*3 - case x: ch2.read => x*2 - case _: q.read => - select.exit(1) - } - } - val chs2 = select.afold(0){ (n,s) => - s match { - case x:chs.closeless.read => - n + x - case _:chs.done => - select.exit(n) + } + + + test("check for done signal from unbuffered channel") { + val ch = makeChannel[Int]() + val sf = select.afold((0)){ (x,s) => + s.select{ + case v: ch.read => x + v + case v: ch.done.read => s.done(x) + } } - } - // note, that if we want call of quit after last write, - // ch1 and ch2 must be unbuffered. - val sendf = for{ _ <- ch1.awriteAll(1 to 10) + val f1 = ch.awriteAll(1 to 5) map (_ =>ch.close) + async { + val r = await(sf) + assert(r==15) + } + } + + + test("check for done signal from buffered channel") { + val ch = makeChannel[Int](10) + val sf = select.afold((0)){ (x,s) => + s.select { + case v: ch.read => x + v + case c: ch.done.read => s.done(x) + } + } + val f1 = async { + ch.writeAll(1 to 5) + // let give all buffers to processe + Time.sleep(200 millis) + ch.close() + } + async { + val r = await(sf) + assert(r == 15) + } + } + + + test("check for done signal from select map") { + val ch1 = makeChannel[Int]() + val ch2 = makeChannel[Int]() + val q = makeChannel[Boolean]() + async{ + val chs: ReadChannel[Future,Int] = for(s <- select) yield { + s.select{ + case x: ch1.read => x*3 + case x: ch2.read => x*2 + case x: q.read => + throw ChannelClosedException() + } + } + val chs2 = select.afold(0){ (n,s) => + s.select{ + case x:chs.read => + n + x + case x:chs.done.read => + s.done(n) + } + } + // note, that if we want call of quit after last write, + // ch1 and ch2 must be unbuffered. + val sendf = for{ _ <- ch1.awriteAll(1 to 10) _ <- ch2.awriteAll(1 to 10) _ <- q.awrite(true) } yield 1 - val r = Await.result(chs2,1 second) - assert( r == (1 to 10).map(_ * 5).sum + 1) - } - + val r = await(chs2) + assert( r == (1 to 10).map(_ * 5).sum + 1) + } + } - lazy val gopherApi = CommonTestObjects.gopherApi - */ } From 86d9fd7a1d3f55d28692cb4c7199a633082883e4 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Sun, 24 Jan 2021 11:44:56 +0200 Subject: [PATCH 035/161] porteed relevant parts from FlowTerminationSuite --- .../channels/FlowTerminationSuite.scala | 79 ------------------- shared/src/main/scala/gopher/Select.scala | 29 ++++++- .../src/main/scala/gopher/SelectForever.scala | 43 ++++++++++ .../main/scala/gopher/SelectListeners.scala | 54 +++++++++++++ shared/src/main/scala/gopher/SelectLoop.scala | 67 +--------------- .../channels/ForeverTerminationSuite.scala | 38 +++++++++ 6 files changed, 163 insertions(+), 147 deletions(-) delete mode 100644 0.99.x/src/test/scala/gopher/channels/FlowTerminationSuite.scala create mode 100644 shared/src/main/scala/gopher/SelectForever.scala create mode 100644 shared/src/test/scala/gopher/channels/ForeverTerminationSuite.scala diff --git a/0.99.x/src/test/scala/gopher/channels/FlowTerminationSuite.scala b/0.99.x/src/test/scala/gopher/channels/FlowTerminationSuite.scala deleted file mode 100644 index 0af31c4f..00000000 --- a/0.99.x/src/test/scala/gopher/channels/FlowTerminationSuite.scala +++ /dev/null @@ -1,79 +0,0 @@ -package gopher.channels - - -import gopher._ -import org.scalatest._ - -import scala.concurrent.{Channel=>_,_} -import scala.language.postfixOps - -class FlowTerminationSuite extends AsyncFunSuite -{ - - - - test("flowTermination covariance assignment") { - - val fUnit = PromiseFlowTermination[Unit]() - // val fAny: FlowTermination[Any] = fUnit - - val f_ : FlowTermination[_] = fUnit - val qq = f_ - - - assert(true) - } - - - test("select with queue type") { - import gopherApi.{gopherExecutionContext => _, _} - - val channel = makeChannel[Int](100) - - val producer = channel.awriteAll(1 to 1000) - - var sum = 0; - val consumer = { - val sc = new Selector[Unit](gopherApi) - def f(self: ContRead[Int,Unit]):Option[ContRead.In[Int]=>Future[Continuated[Unit]]] = - { - Some { - case ContRead.Value(a) => sum = sum + a - if (a == 1000) sc.doExit(()) - Future successful self - case ContRead.Failure(e) => Future failed e - case _ => - Future successful self - } - } - sc.addReader(channel,f) - sc.run - } - - // main - that we finished - consumer map (u => assert(true)) - - } - - - test("not propagate signals after exit") { - - import gopherApi.{gopherExecutionContext => _, _} - val channel = makeChannel[Int](100) - var sum = 0 - val f0 = select.forever { - case x: channel.read => sum += x - select.shutdown() - } - for {r2 <- channel.awrite(1) - r3 <- channel.awrite(2) - r0 <- f0 } yield assert(sum == 1) - - } - - - val gopherApi = CommonTestObjects.gopherApi - -} - - diff --git a/shared/src/main/scala/gopher/Select.scala b/shared/src/main/scala/gopher/Select.scala index d965d99a..a3140ffb 100644 --- a/shared/src/main/scala/gopher/Select.scala +++ b/shared/src/main/scala/gopher/Select.scala @@ -62,20 +62,27 @@ class Select[F[_]:CpsSchedulingMonad](api: Gopher[F]): var done = false while(!done) val g = SelectGroup[F,A](api) - try + try { val e = await(step(g)) r.write(e) - catch + } catch { case ex: ChannelClosedException => r.close() done=true + } } } r - - + def forever: SelectForever[F] = new SelectForever[F](api) + + inline def aforever(inline pf: PartialFunction[Any,Unit]): F[Unit] = + async { + val runner = new SelectForever[F](api) + runner.apply(pf) + } + @@ -131,6 +138,20 @@ object Select: runImpl( builder, pf) + def foreverImpl[F[_]:Type](pf: Expr[PartialFunction[Any,Unit]], api:Expr[Gopher[F]])(using Quotes): Expr[Unit] = + def builder(caseDefs: List[SelectorCaseExpr[F,Unit]]):Expr[Unit] = { + val s0 = '{ + new SelectForever[F]($api)(using ${api}.asyncMonad) + } + val g: Expr[SelectForever[F]] = caseDefs.foldLeft(s0){(s,e) => + e.appended(s) + } + val r = '{ $g.run() } + r.asExprOf[Unit] + } + runImpl(builder, pf) + + def runImpl[F[_]:Type, A:Type,B :Type](builder: List[SelectorCaseExpr[F,A]]=>Expr[B], pf: Expr[PartialFunction[Any,A]])(using Quotes): Expr[B] = diff --git a/shared/src/main/scala/gopher/SelectForever.scala b/shared/src/main/scala/gopher/SelectForever.scala new file mode 100644 index 00000000..403e27f5 --- /dev/null +++ b/shared/src/main/scala/gopher/SelectForever.scala @@ -0,0 +1,43 @@ +package gopher + +import cps._ +import scala.quoted._ +import scala.compiletime._ +import scala.concurrent.duration._ + + +class SelectForever[F[_]:CpsSchedulingMonad](api: Gopher[F]) extends SelectGroupBuilder[F,Unit](api): + + + inline def apply(inline pf: PartialFunction[Any,Unit]): Unit = + ${ + Select.foreverImpl('pf,'api) + } + + def runAsync(): F[Unit] = async[F]{ + while{ + val group = api.select.group[Unit] + try + groupBuilder(group).run() + true + catch + case ex: ChannelClosedException => + false + } do () + } + + inline def run(): Unit = await(runAsync()) + + + + + + + + + + + + + + diff --git a/shared/src/main/scala/gopher/SelectListeners.scala b/shared/src/main/scala/gopher/SelectListeners.scala index 6d5c2b0b..d73be38d 100644 --- a/shared/src/main/scala/gopher/SelectListeners.scala +++ b/shared/src/main/scala/gopher/SelectListeners.scala @@ -11,6 +11,60 @@ trait SelectListeners[F[_],S]: def onTimeout(t: FiniteDuration)(f: FiniteDuration => S): this.type +abstract class SelectGroupBuilder[F[_],S](api: Gopher[F]) extends SelectListeners[F,S]: + + protected var groupBuilder: SelectGroup[F,S] => SelectGroup[F,S] = identity + + + def onRead[A](ch: ReadChannel[F,A])(f: A => S): this.type = + groupBuilder = groupBuilder.andThen{ + g => g.onRead(ch)(f) + } + this + + def onReadAsync[A](ch: ReadChannel[F,A])(f: A => F[S]): this.type = + groupBuilder = groupBuilder.andThen( _.onReadAsync(ch)(f) ) + this + + + inline def reading[A](ch: ReadChannel[F,A])(f: A=>S): this.type = + onRead(ch)(f) + + def onWrite[A](ch: WriteChannel[F,A], a: =>A)(f: A=>S): this.type = + groupBuilder = groupBuilder.andThen{ + g => g.onWrite(ch,a)(f) + } + this + + def onWriteAsync[A](ch: WriteChannel[F,A], a: =>A)(f: A=>F[S]): this.type = + groupBuilder = groupBuilder.andThen{ + g => g.onWriteAsync(ch,a)(f) + } + this + + + inline def writing[A](ch: WriteChannel[F,A], a: =>A)(f: A=>S): this.type = + onWrite(ch,a)(f) + + + def onTimeout(t: FiniteDuration)(f: FiniteDuration => S): this.type = + groupBuilder = groupBuilder.andThen{ + g => g.onTimeout(t)(f) + } + this + + def onTimeoutAsync(t: FiniteDuration)(f: FiniteDuration => F[S]): this.type = + groupBuilder = groupBuilder.andThen{ + g => g.onTimeoutAsync(t)(f) + } + this + + + + + + + diff --git a/shared/src/main/scala/gopher/SelectLoop.scala b/shared/src/main/scala/gopher/SelectLoop.scala index 72c3f2eb..20f8535c 100644 --- a/shared/src/main/scala/gopher/SelectLoop.scala +++ b/shared/src/main/scala/gopher/SelectLoop.scala @@ -5,81 +5,20 @@ import scala.quoted._ import scala.compiletime._ import scala.concurrent.duration._ -class SelectLoop[F[_]:CpsSchedulingMonad](api: Gopher[F]) extends SelectListeners[F,Boolean]: +class SelectLoop[F[_]:CpsSchedulingMonad](api: Gopher[F]) extends SelectGroupBuilder[F,Boolean](api): - private var groupBuilder: SelectGroup[F,Boolean] => SelectGroup[F,Boolean] = identity - - def onRead[A](ch: ReadChannel[F,A])(f: A => Boolean): this.type = - groupBuilder = groupBuilder.andThen{ - g => g.onRead(ch)(f) - } - this - - // TODO: think about special notation for builders - def onReadAsync[A](ch: ReadChannel[F,A])(f: A => F[Boolean]): this.type = - groupBuilder = groupBuilder.andThen( _.onReadAsync(ch)(f) ) - this - - // TODO: think about special notation for builders - def onRead_async[A](ch: ReadChannel[F,A])(f: A => F[Boolean]): F[this.type] = - summon[CpsMonad[F]].pure(onReadAsync(ch)(f)) - - inline def reading[A](ch: ReadChannel[F,A])(f: A=>Boolean): this.type = - onRead(ch)(f) - - def onWrite[A](ch: WriteChannel[F,A], a: =>A)(f: A=>Boolean): this.type = - groupBuilder = groupBuilder.andThen{ - g => g.onWrite(ch,a)(f) - } - this - - def onWriteAsync[A](ch: WriteChannel[F,A], a: =>A)(f: A=>F[Boolean]): this.type = - groupBuilder = groupBuilder.andThen{ - g => g.onWriteAsync(ch,a)(f) - } - this - - def onWrite_async[A](ch: WriteChannel[F,A], fa: ()=>F[A])(f: A=>F[Boolean]): F[this.type] = - api.asyncMonad.map(fa())(a => onWriteAsync(ch,a)(f)) - - inline def writing[A](ch: WriteChannel[F,A], a: =>A)(f: A=>Boolean): this.type = - onWrite(ch,a)(f) - - - def onTimeout(t: FiniteDuration)(f: FiniteDuration => Boolean): this.type = - groupBuilder = groupBuilder.andThen{ - g => g.onTimeout(t)(f) - } - this - - def onTimeoutAsync(t: FiniteDuration)(f: FiniteDuration => F[Boolean]): this.type = - groupBuilder = groupBuilder.andThen{ - g => g.onTimeoutAsync(t)(f) - } - this - - - def onTimeout_async(t: FiniteDuration)(f: FiniteDuration => F[Boolean]): F[this.type] = - summon[CpsMonad[F]].pure(onTimeoutAsync(t)(f)) inline def apply(inline pf: PartialFunction[Any,Boolean]): Unit = ${ Select.loopImpl[F]('pf, 'api ) - } - - - + } + def runAsync(): F[Unit] = async[F] { - try while{ val group = api.select.group[Boolean] val r = groupBuilder(group).run() r } do () - catch - case ex:Throwable => - // TODO: log - ex.printStackTrace() } inline def run(): Unit = await(runAsync()) diff --git a/shared/src/test/scala/gopher/channels/ForeverTerminationSuite.scala b/shared/src/test/scala/gopher/channels/ForeverTerminationSuite.scala new file mode 100644 index 00000000..0a5fa69a --- /dev/null +++ b/shared/src/test/scala/gopher/channels/ForeverTerminationSuite.scala @@ -0,0 +1,38 @@ +package gopher.channels + + +import cps._ +import gopher._ +import munit._ + +import scala.concurrent.{Channel=>_,_} +import scala.language.postfixOps + +import cps.monads.FutureAsyncMonad + + +class ForeverSuite extends FunSuite +{ + + import ExecutionContext.Implicits.global + given Gopher[Future] = SharedGopherAPI.apply[Future]() + + + test("forevr not propagate signals after exit") { + val channel = makeChannel[Int](100) + var sum = 0 + val f0 = select.aforever { + case x: channel.read => sum += x + throw ChannelClosedException() + } + for {r2 <- channel.awrite(1) + r3 <- channel.awrite(2) + r0 <- f0 } yield assert(sum == 1) + + } + + + +} + + From c8dc8628f794c5672cabd381fbb2ed4b7607b32b Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Thu, 28 Jan 2021 09:43:50 +0200 Subject: [PATCH 036/161] - workarround for compiler bug with incorct ownity of inline functions. - ported Filtered channel - FoldSelectSuite port in progress --- build.sbt | 2 +- shared/src/main/scala/gopher/Channel.scala | 12 ++- .../src/main/scala/gopher/ReadChannel.scala | 11 ++- shared/src/main/scala/gopher/Select.scala | 86 +++++++++--------- .../src/main/scala/gopher/SelectForever.scala | 29 +++--- .../src/main/scala/gopher/SelectGroup.scala | 8 +- .../main/scala/gopher/SelectListeners.scala | 18 +++- shared/src/main/scala/gopher/SelectLoop.scala | 9 +- .../src/main/scala/gopher/WriteChannel.scala | 8 +- .../scala/gopher/impl/FilteredChannel.scala | 22 +++++ .../gopher/impl/FilteredReadChannel.scala | 89 +++++++++++++++++++ .../scala/gopher/impl/MappedReadChannel.scala | 3 +- .../src/main/scala/gopher/impl/Reader.scala | 2 +- .../gopher/channels/FoldSelectSuite.scala | 45 ++++++---- 14 files changed, 249 insertions(+), 95 deletions(-) create mode 100644 shared/src/main/scala/gopher/impl/FilteredChannel.scala create mode 100644 shared/src/main/scala/gopher/impl/FilteredReadChannel.scala rename {0.99.x => shared}/src/test/scala/gopher/channels/FoldSelectSuite.scala (65%) diff --git a/build.sbt b/build.sbt index b9594ec3..ee79d917 100644 --- a/build.sbt +++ b/build.sbt @@ -30,7 +30,7 @@ lazy val gopher = crossProject(JSPlatform, JVMPlatform) .settings(sharedSettings) .disablePlugins(SitePlugin) .jvmSettings( - scalacOptions ++= Seq( "-unchecked", "-Ycheck:macro" ), + scalacOptions ++= Seq( "-unchecked", "-Ycheck:macros" ), ).jsSettings( libraryDependencies += ("org.scala-js" %%% "scalajs-java-logging" % "1.0.0").withDottyCompat(scalaVersion.value), // TODO: switch to ModuleES ? diff --git a/shared/src/main/scala/gopher/Channel.scala b/shared/src/main/scala/gopher/Channel.scala index 6cd1fd17..40f6877b 100644 --- a/shared/src/main/scala/gopher/Channel.scala +++ b/shared/src/main/scala/gopher/Channel.scala @@ -16,11 +16,21 @@ trait Channel[F[_],W,R] extends WriteChannel[F,W] with ReadChannel[F,R] with Clo override def map[R1](f: R=>R1): Channel[F,W,R1] = MappedChannel(this,f) - //override def mapAsync[R1](f: R=>F[R1]): Channel[F,W,R1] = ??? + override def mapAsync[R1](f: R=>F[R1]): Channel[F,W,R1] = + MappedAsyncChannel(this, f) def flatMap[R1](f: R=> ReadChannel[F,R1]): Channel[F,W,R1] = ChFlatMappedChannel(this,f) + //def flatMapAsync[R1](f: R=> F[ReadChannel[F,R1]]): Channel[F,W,R1] = + // ChFlatMappedAsyncChannel(this,f) + + override def filter(p: R=>Boolean): Channel[F,W,R] = + FilteredChannel(this, p) + + override def filterAsync(p: R=>F[Boolean]): Channel[F,W,R] = + FilteredAsyncChannel(this,p) + end Channel diff --git a/shared/src/main/scala/gopher/ReadChannel.scala b/shared/src/main/scala/gopher/ReadChannel.scala index a80e4d71..32625418 100644 --- a/shared/src/main/scala/gopher/ReadChannel.scala +++ b/shared/src/main/scala/gopher/ReadChannel.scala @@ -91,7 +91,7 @@ trait ReadChannel[F[_], A]: * run code each time when new object is arriced. * until end of stream is not reached **/ - inline def foreach(f: A=>Unit): Unit = + inline def foreach(inline f: A=>Unit): Unit = await(aforeach(f))(using rAsyncMonad) def map[B](f: A=>B): ReadChannel[F,B] = @@ -100,6 +100,13 @@ trait ReadChannel[F[_], A]: def mapAsync[B](f: A=>F[B]): ReadChannel[F,B] = new MappedAsyncReadChannel(this, f) + + def filter(p: A=>Boolean): ReadChannel[F,A] = + new FilteredReadChannel(this,p) + + def filterAsync(p: A=>F[Boolean]): ReadChannel[F,A] = + new FilteredAsyncReadChannel(this,p) + def dup(bufSize: Int=1, expiration: Duration=Duration.Inf): (ReadChannel[F,A], ReadChannel[F,A]) = DuppedInput(this, bufSize)(using gopherApi).pair @@ -121,7 +128,7 @@ trait ReadChannel[F[_], A]: s } - inline def fold[S](s0:S)(f: (S,A) => S ): S = + inline def fold[S](inline s0:S)(inline f: (S,A) => S ): S = await[F,S](afold(s0)(f))(using rAsyncMonad) class DoneReadChannel extends ReadChannel[F,Unit]: diff --git a/shared/src/main/scala/gopher/Select.scala b/shared/src/main/scala/gopher/Select.scala index a3140ffb..376f346d 100644 --- a/shared/src/main/scala/gopher/Select.scala +++ b/shared/src/main/scala/gopher/Select.scala @@ -7,7 +7,7 @@ import scala.compiletime._ import scala.concurrent.duration._ -class Select[F[_]:CpsSchedulingMonad](api: Gopher[F]): +class Select[F[_]](api: Gopher[F]): inline def apply[A](inline pf: PartialFunction[Any,A]): A = ${ @@ -57,6 +57,7 @@ class Select[F[_]:CpsSchedulingMonad](api: Gopher[F]): def mapAsync[A](step: SelectGroup[F,A] => F[A]): ReadChannel[F,A] = val r = makeChannel[A]()(using api) + given CpsSchedulingMonad[F] = api.asyncMonad api.asyncMonad.spawn{ async{ var done = false @@ -74,10 +75,10 @@ class Select[F[_]:CpsSchedulingMonad](api: Gopher[F]): } r - def forever: SelectForever[F] = new SelectForever[F](api) + def forever: SelectForever[F] = new SelectForever[F](api ) inline def aforever(inline pf: PartialFunction[Any,Unit]): F[Unit] = - async { + async(using api.asyncMonad).apply { val runner = new SelectForever[F](api) runner.apply(pf) } @@ -88,78 +89,77 @@ class Select[F[_]:CpsSchedulingMonad](api: Gopher[F]): object Select: - sealed trait SelectGroupExpr[F[_],S]: - def toExprOf[X <: SelectListeners[F,S]]: Expr[X] + sealed trait SelectGroupExpr[F[_],S, R]: + def toExprOf[X <: SelectListeners[F,S, R]]: Expr[X] - sealed trait SelectorCaseExpr[F[_]:Type, S:Type]: + sealed trait SelectorCaseExpr[F[_]:Type, S:Type, R:Type]: type Monad[X] = F[X] - def appended[L <: SelectListeners[F,S] : Type](base: Expr[L])(using Quotes): Expr[L] + def appended[L <: SelectListeners[F,S,R] : Type](base: Expr[L])(using Quotes): Expr[L] - case class ReadExpression[F[_]:Type, A:Type, S:Type](ch: Expr[ReadChannel[F,A]], f: Expr[A => S]) extends SelectorCaseExpr[F,S]: - def appended[L <: SelectListeners[F,S]: Type](base: Expr[L])(using Quotes): Expr[L] = + case class ReadExpression[F[_]:Type, A:Type, S:Type, R:Type](ch: Expr[ReadChannel[F,A]], f: Expr[A => S]) extends SelectorCaseExpr[F,S,R]: + def appended[L <: SelectListeners[F,S,R]: Type](base: Expr[L])(using Quotes): Expr[L] = '{ $base.onRead($ch)($f) } - case class WriteExpression[F[_]:Type, A:Type, S:Type](ch: Expr[WriteChannel[F,A]], a: Expr[A], f: Expr[A => S]) extends SelectorCaseExpr[F,S]: - def appended[L <: SelectListeners[F,S]: Type](base: Expr[L])(using Quotes): Expr[L] = + case class WriteExpression[F[_]:Type, A:Type, S:Type, R:Type](ch: Expr[WriteChannel[F,A]], a: Expr[A], f: Expr[A => S]) extends SelectorCaseExpr[F,S,R]: + def appended[L <: SelectListeners[F,S,R]: Type](base: Expr[L])(using Quotes): Expr[L] = '{ $base.onWrite($ch,$a)($f) } - case class TimeoutExpression[F[_]:Type,S:Type](t: Expr[FiniteDuration], f: Expr[ FiniteDuration => S ]) extends SelectorCaseExpr[F,S]: - def appended[L <: SelectListeners[F,S]: Type](base: Expr[L])(using Quotes): Expr[L] = + case class TimeoutExpression[F[_]:Type,S:Type, R:Type](t: Expr[FiniteDuration], f: Expr[ FiniteDuration => S ]) extends SelectorCaseExpr[F,S,R]: + def appended[L <: SelectListeners[F,S,R]: Type](base: Expr[L])(using Quotes): Expr[L] = '{ $base.onTimeout($t)($f) } - case class DoneExression[F[_]:Type, A:Type, S:Type](ch: Expr[ReadChannel[F,A]], f: Expr[Unit=>S]) extends SelectorCaseExpr[F,S]: - def appended[L <: SelectListeners[F,S]: Type](base: Expr[L])(using Quotes): Expr[L] = + case class DoneExression[F[_]:Type, A:Type, S:Type, R:Type](ch: Expr[ReadChannel[F,A]], f: Expr[Unit=>S]) extends SelectorCaseExpr[F,S,R]: + def appended[L <: SelectListeners[F,S,R]: Type](base: Expr[L])(using Quotes): Expr[L] = '{ $base.onRead($ch.done)($f) } + def selectListenerBuilder[F[_]:Type, S:Type, R:Type, L <: SelectListeners[F,S,R]:Type]( + constructor: Expr[L], caseDefs: List[SelectorCaseExpr[F,S,R]], api:Expr[Gopher[F]])(using Quotes): Expr[R] = + val s0 = constructor + val g = caseDefs.foldLeft(s0){(s,e) => + e.appended(s) + } + // dotty bug if g.run + val r = '{ await($g.runAsync())(using ${api}.asyncMonad) } + r.asExprOf[R] + + def onceImpl[F[_]:Type, A:Type](pf: Expr[PartialFunction[Any,A]], api: Expr[Gopher[F]])(using Quotes): Expr[A] = - def builder(caseDefs: List[SelectorCaseExpr[F,A]]):Expr[A] = { + def builder(caseDefs: List[SelectorCaseExpr[F,A,A]]):Expr[A] = { val s0 = '{ - new SelectGroup[F,A]($api)(using ${api}.asyncMonad) - } - val g: Expr[SelectGroup[F,A]] = caseDefs.foldLeft(s0){(s,e) => - e.appended(s) + new SelectGroup[F,A]($api) } - val r = '{ $g.run() } - r.asExprOf[A] + selectListenerBuilder(s0, caseDefs, api) } - runImpl( builder, pf) + runImpl(builder, pf) def loopImpl[F[_]:Type](pf: Expr[PartialFunction[Any,Boolean]], api: Expr[Gopher[F]])(using Quotes): Expr[Unit] = - def builder(caseDefs: List[SelectorCaseExpr[F,Boolean]]):Expr[Unit] = { + def builder(caseDefs: List[SelectorCaseExpr[F,Boolean,Unit]]):Expr[Unit] = { val s0 = '{ - new SelectLoop[F]($api)(using ${api}.asyncMonad) + new SelectLoop[F]($api) } - val g: Expr[SelectLoop[F]] = caseDefs.foldLeft(s0){(s,e) => - e.appended(s) - } - val r = '{ $g.run() } - r.asExprOf[Unit] + selectListenerBuilder(s0, caseDefs, api) } runImpl( builder, pf) def foreverImpl[F[_]:Type](pf: Expr[PartialFunction[Any,Unit]], api:Expr[Gopher[F]])(using Quotes): Expr[Unit] = - def builder(caseDefs: List[SelectorCaseExpr[F,Unit]]):Expr[Unit] = { + def builder(caseDefs: List[SelectorCaseExpr[F,Unit,Unit]]):Expr[Unit] = { val s0 = '{ - new SelectForever[F]($api)(using ${api}.asyncMonad) - } - val g: Expr[SelectForever[F]] = caseDefs.foldLeft(s0){(s,e) => - e.appended(s) + new SelectForever[F]($api) } - val r = '{ $g.run() } - r.asExprOf[Unit] + selectListenerBuilder(s0, caseDefs, api) } runImpl(builder, pf) - def runImpl[F[_]:Type, A:Type,B :Type](builder: List[SelectorCaseExpr[F,A]]=>Expr[B], + def runImpl[F[_]:Type, A:Type,B :Type](builder: List[SelectorCaseExpr[F,A,B]]=>Expr[B], pf: Expr[PartialFunction[Any,A]])(using Quotes): Expr[B] = import quotes.reflect._ runImplTree[F,A,B](builder, pf.asTerm) def runImplTree[F[_]:Type, A:Type, B:Type](using Quotes)( - builder: List[SelectorCaseExpr[F,A]] => Expr[B], + builder: List[SelectorCaseExpr[F,A,B]] => Expr[B], pf: quotes.reflect.Term ): Expr[B] = import quotes.reflect._ @@ -173,15 +173,15 @@ object Select: //if (caseExprs.find(_.isInstanceOf[DefaultExpression[?]]).isDefined) { // report.error("default is not supported") //} - builder(cases.map(parseCaseDef[F,A](_))) + builder(cases.map(parseCaseDef[F,A,B](_))) - def parseCaseDef[F[_]:Type,S:Type](using Quotes)(caseDef: quotes.reflect.CaseDef): SelectorCaseExpr[F,S] = + def parseCaseDef[F[_]:Type,S:Type,R:Type](using Quotes)(caseDef: quotes.reflect.CaseDef): SelectorCaseExpr[F,S,R] = import quotes.reflect._ val caseDefGuard = parseCaseDefGuard(caseDef) - def handleRead(bind: Bind, valName: String, channel:Term, tp:TypeRepr): SelectorCaseExpr[F,S] = + def handleRead(bind: Bind, valName: String, channel:Term, tp:TypeRepr): SelectorCaseExpr[F,S,R] = val readFun = makeLambda(valName,tp,bind.symbol,caseDef.rhs) if (channel.tpe <:< TypeRepr.of[ReadChannel[F,?]]) tp.asType match @@ -191,7 +191,7 @@ object Select: else reportError("read pattern is not a read channel", channel.asExpr) - def handleWrite(bind: Bind, valName: String, channel:Term, tp:TypeRepr): SelectorCaseExpr[F,S] = + def handleWrite(bind: Bind, valName: String, channel:Term, tp:TypeRepr): SelectorCaseExpr[F,S,R] = val writeFun = makeLambda(valName,tp, bind.symbol, caseDef.rhs) val e = caseDefGuard.getOrElse(valName, reportError(s"not found binding ${valName} in write condition", caseDef.pattern.asExpr) diff --git a/shared/src/main/scala/gopher/SelectForever.scala b/shared/src/main/scala/gopher/SelectForever.scala index 403e27f5..7c30518e 100644 --- a/shared/src/main/scala/gopher/SelectForever.scala +++ b/shared/src/main/scala/gopher/SelectForever.scala @@ -6,7 +6,7 @@ import scala.compiletime._ import scala.concurrent.duration._ -class SelectForever[F[_]:CpsSchedulingMonad](api: Gopher[F]) extends SelectGroupBuilder[F,Unit](api): +class SelectForever[F[_]](api: Gopher[F]) extends SelectGroupBuilder[F,Unit, Unit](api): inline def apply(inline pf: PartialFunction[Any,Unit]): Unit = @@ -14,19 +14,20 @@ class SelectForever[F[_]:CpsSchedulingMonad](api: Gopher[F]) extends SelectGroup Select.foreverImpl('pf,'api) } - def runAsync(): F[Unit] = async[F]{ - while{ - val group = api.select.group[Unit] - try - groupBuilder(group).run() - true - catch - case ex: ChannelClosedException => - false - } do () - } - - inline def run(): Unit = await(runAsync()) + def runAsync(): F[Unit] = + given CpsSchedulingMonad[F] = api.asyncMonad + async[F]{ + while{ + val group = api.select.group[Unit] + try + groupBuilder(group).run() + true + catch + case ex: ChannelClosedException => + false + } do () + } + diff --git a/shared/src/main/scala/gopher/SelectGroup.scala b/shared/src/main/scala/gopher/SelectGroup.scala index 2dd6ea08..da0a3025 100644 --- a/shared/src/main/scala/gopher/SelectGroup.scala +++ b/shared/src/main/scala/gopher/SelectGroup.scala @@ -15,7 +15,7 @@ import scala.language.postfixOps * Select group is a virtual 'lock' object, where only * ne fro rieader and writer can exists at the sae time. **/ -class SelectGroup[F[_]:CpsSchedulingMonad, S](api: Gopher[F]) extends SelectListeners[F,S]: +class SelectGroup[F[_], S](api: Gopher[F]) extends SelectListeners[F,S,S]: thisSelectGroup => @@ -27,10 +27,12 @@ class SelectGroup[F[_]:CpsSchedulingMonad, S](api: Gopher[F]) extends SelectLis **/ val waitState: AtomicInteger = new AtomicInteger(0) var call: Try[S] => Unit = { _ => () } - private inline def m = summon[CpsSchedulingMonad[F]] + private inline def m = api.asyncMonad val retval = m.adoptCallbackStyle[S](f => call=f) val startTime = new AtomicLong(0L) var timeoutScheduled: Option[Time.Scheduled] = None + + override def asyncMonad = api.asyncMonad def addReader[A](ch: ReadChannel[F,A], action: Try[A]=>F[S]): Unit = val record = ReaderRecord(ch, action) @@ -59,8 +61,6 @@ class SelectGroup[F[_]:CpsSchedulingMonad, S](api: Gopher[F]) extends SelectLis def runAsync():F[S] = retval - inline def run(): S = await(step()) - inline def apply(inline pf: PartialFunction[Any,S]): S = ${ Select.onceImpl[F,S]('pf, 'api ) diff --git a/shared/src/main/scala/gopher/SelectListeners.scala b/shared/src/main/scala/gopher/SelectListeners.scala index d73be38d..81a1840c 100644 --- a/shared/src/main/scala/gopher/SelectListeners.scala +++ b/shared/src/main/scala/gopher/SelectListeners.scala @@ -1,8 +1,10 @@ package gopher +import cps._ import scala.concurrent.duration.FiniteDuration -trait SelectListeners[F[_],S]: +trait SelectListeners[F[_],S, R]: + def onRead[A](ch: ReadChannel[F,A]) (f: A => S ): this.type @@ -10,12 +12,19 @@ trait SelectListeners[F[_],S]: def onTimeout(t: FiniteDuration)(f: FiniteDuration => S): this.type + def asyncMonad: CpsSchedulingMonad[F] + + def runAsync():F[R] + + inline def run(): R = await(runAsync())(using asyncMonad) + + -abstract class SelectGroupBuilder[F[_],S](api: Gopher[F]) extends SelectListeners[F,S]: + +abstract class SelectGroupBuilder[F[_],S, R](api: Gopher[F]) extends SelectListeners[F,S, R]: protected var groupBuilder: SelectGroup[F,S] => SelectGroup[F,S] = identity - def onRead[A](ch: ReadChannel[F,A])(f: A => S): this.type = groupBuilder = groupBuilder.andThen{ g => g.onRead(ch)(f) @@ -59,7 +68,8 @@ abstract class SelectGroupBuilder[F[_],S](api: Gopher[F]) extends SelectListener } this - + def asyncMonad: CpsSchedulingMonad[F] = api.asyncMonad + diff --git a/shared/src/main/scala/gopher/SelectLoop.scala b/shared/src/main/scala/gopher/SelectLoop.scala index 20f8535c..854f7a6d 100644 --- a/shared/src/main/scala/gopher/SelectLoop.scala +++ b/shared/src/main/scala/gopher/SelectLoop.scala @@ -5,7 +5,7 @@ import scala.quoted._ import scala.compiletime._ import scala.concurrent.duration._ -class SelectLoop[F[_]:CpsSchedulingMonad](api: Gopher[F]) extends SelectGroupBuilder[F,Boolean](api): +class SelectLoop[F[_]](api: Gopher[F]) extends SelectGroupBuilder[F,Boolean, Unit](api): inline def apply(inline pf: PartialFunction[Any,Boolean]): Unit = @@ -13,15 +13,16 @@ class SelectLoop[F[_]:CpsSchedulingMonad](api: Gopher[F]) extends SelectGroupBui Select.loopImpl[F]('pf, 'api ) } - def runAsync(): F[Unit] = async[F] { + def runAsync(): F[Unit] = + given CpsSchedulingMonad[F] = api.asyncMonad + async[F]{ while{ val group = api.select.group[Boolean] val r = groupBuilder(group).run() r } do () - } + } - inline def run(): Unit = await(runAsync()) diff --git a/shared/src/main/scala/gopher/WriteChannel.scala b/shared/src/main/scala/gopher/WriteChannel.scala index 09e53a81..5083e28c 100644 --- a/shared/src/main/scala/gopher/WriteChannel.scala +++ b/shared/src/main/scala/gopher/WriteChannel.scala @@ -22,13 +22,13 @@ trait WriteChannel[F[_], A]: // inline def apply(a:A): Unit = await(awrite(a))(using asyncMonad) // inline def unapply(a:A): Some[A] = ??? - inline def write(a:A): Unit = await(awrite(a))(using asyncMonad) + inline def write(inline a:A): Unit = await(awrite(a))(using asyncMonad) @targetName("write1") - inline def <~ (a:A): Unit = await(awrite(a))(using asyncMonad) + inline def <~ (inline a:A): Unit = await(awrite(a))(using asyncMonad) @targetName("write2") - inline def ! (a:A): Unit = await(awrite(a))(using asyncMonad) + inline def ! (inline a:A): Unit = await(awrite(a))(using asyncMonad) //def Write(x:A):WritePattern = new WritePattern(x) @@ -50,7 +50,7 @@ trait WriteChannel[F[_], A]: } } - inline def writeAll(collection: IterableOnce[A]): Unit = + inline def writeAll(inline collection: IterableOnce[A]): Unit = await(awriteAll(collection))(using asyncMonad) diff --git a/shared/src/main/scala/gopher/impl/FilteredChannel.scala b/shared/src/main/scala/gopher/impl/FilteredChannel.scala new file mode 100644 index 00000000..ad460434 --- /dev/null +++ b/shared/src/main/scala/gopher/impl/FilteredChannel.scala @@ -0,0 +1,22 @@ +package gopher.impl + +import gopher._ + +class FilteredChannel[F[_],W,R](internal: Channel[F,W,R], p: R => Boolean) extends FilteredReadChannel[F,R](internal, p) + with Channel[F,W,R]: + + override def addWriter(writer: Writer[W]): Unit = + internal.addWriter(writer) + + override def close(): Unit = + internal.close() + + +class FilteredAsyncChannel[F[_],W,R](internal: Channel[F,W,R], p: R => F[Boolean]) extends FilteredAsyncReadChannel[F,R](internal, p) + with Channel[F,W,R]: + + override def addWriter(writer: Writer[W]): Unit = + internal.addWriter(writer) + + override def close(): Unit = + internal.close() diff --git a/shared/src/main/scala/gopher/impl/FilteredReadChannel.scala b/shared/src/main/scala/gopher/impl/FilteredReadChannel.scala new file mode 100644 index 00000000..1bab01b1 --- /dev/null +++ b/shared/src/main/scala/gopher/impl/FilteredReadChannel.scala @@ -0,0 +1,89 @@ +package gopher.impl + +import gopher._ + +import scala.util._ + +class FilteredReadChannel[F[_],A](internal: ReadChannel[F,A], p: A=>Boolean) extends ReadChannel[F,A] { + + + class FilteredReader(nested: Reader[A]) extends Reader[A] { + + def wrappedFun(fun: (Try[A] => Unit) ): (Try[A] => Unit) = { + case Success(a) => + if (p(a)) + fun(Success(a)) + case Failure(ex) => + fun(Failure(ex)) + } + + override def capture(): Option[Try[A]=>Unit] = + nested.capture().map{ fun => + wrappedFun(fun) + } + + override def canExpire: Boolean = nested.canExpire + + override def isExpired: Boolean = nested.isExpired + + override def markUsed(): Unit = nested.markUsed() + + override def markFree(): Unit = nested.markFree() + + } + + def addReader(reader: Reader[A]): Unit = + internal.addReader(FilteredReader(reader)) + + def addDoneReader(reader: Reader[Unit]): Unit = internal.addDoneReader(reader) + + def gopherApi:Gopher[F] = internal.gopherApi + +} + + +class FilteredAsyncReadChannel[F[_],A](internal: ReadChannel[F,A], p: A=>F[Boolean]) extends ReadChannel[F,A] { + + + class FilteredReader(nested: Reader[A]) extends Reader[A] { + + def wrappedFun(fun: (Try[A] => Unit) ): (Try[A] => Unit) = { + case Success(a) => + gopherApi.asyncMonad.spawn( + gopherApi.asyncMonad.mapTry(p(a)){ + case Success(v) => + if (v) { + fun(Success(a)) + } + case Failure(ex) => + fun(Failure(ex)) + } + ) + case Failure(ex) => + fun(Failure(ex)) + } + + override def capture(): Option[Try[A]=>Unit] = + nested.capture().map{ fun => + wrappedFun(fun) + } + + override def canExpire: Boolean = nested.canExpire + + override def isExpired: Boolean = nested.isExpired + + override def markUsed(): Unit = nested.markUsed() + + override def markFree(): Unit = nested.markFree() + + } + + def addReader(reader: Reader[A]): Unit = + internal.addReader(FilteredReader(reader)) + + def addDoneReader(reader: Reader[Unit]): Unit = internal.addDoneReader(reader) + + def gopherApi:Gopher[F] = internal.gopherApi + +} + diff --git a/shared/src/main/scala/gopher/impl/MappedReadChannel.scala b/shared/src/main/scala/gopher/impl/MappedReadChannel.scala index b019fae6..27ee86f9 100644 --- a/shared/src/main/scala/gopher/impl/MappedReadChannel.scala +++ b/shared/src/main/scala/gopher/impl/MappedReadChannel.scala @@ -6,7 +6,6 @@ import scala.util.control.NonFatal class MappedReadChannel[F[_],A, B](internal: ReadChannel[F,A], f: A=> B) extends ReadChannel[F,B] { - def addDoneReader(reader: Reader[Unit]): Unit = internal.addDoneReader(reader) class MReader(nested: Reader[B]) extends Reader[A] { @@ -37,6 +36,8 @@ class MappedReadChannel[F[_],A, B](internal: ReadChannel[F,A], f: A=> B) extends def addReader(reader: Reader[B]): Unit = internal.addReader(MReader(reader)) + def addDoneReader(reader: Reader[Unit]): Unit = internal.addDoneReader(reader) + def gopherApi:Gopher[F] = internal.gopherApi } diff --git a/shared/src/main/scala/gopher/impl/Reader.scala b/shared/src/main/scala/gopher/impl/Reader.scala index 48552877..e0baf284 100644 --- a/shared/src/main/scala/gopher/impl/Reader.scala +++ b/shared/src/main/scala/gopher/impl/Reader.scala @@ -2,5 +2,5 @@ package gopher.impl import scala.util.Try -trait Reader[A] extends Expirable[Try[A]=>Unit] +trait Reader[A] extends Expirable[Try[A]=>Unit] diff --git a/0.99.x/src/test/scala/gopher/channels/FoldSelectSuite.scala b/shared/src/test/scala/gopher/channels/FoldSelectSuite.scala similarity index 65% rename from 0.99.x/src/test/scala/gopher/channels/FoldSelectSuite.scala rename to shared/src/test/scala/gopher/channels/FoldSelectSuite.scala index 6c33f47b..03171b6c 100644 --- a/0.99.x/src/test/scala/gopher/channels/FoldSelectSuite.scala +++ b/shared/src/test/scala/gopher/channels/FoldSelectSuite.scala @@ -1,63 +1,74 @@ package gopher.channels - +import cps._ import gopher._ -import org.scalatest._ +import munit._ + +import scala.concurrent.{Channel=>_,_} +import scala.language.postfixOps -import scala.language._ +import cps.monads.FutureAsyncMonad -class FoldSelectSuite extends AsyncFunSuite +class FoldSelectSuite extends FunSuite { - lazy val gopherApi = CommonTestObjects.gopherApi - import gopherApi._ + + import scala.concurrent.ExecutionContext.Implicits.global + given Gopher[Future] = SharedGopherAPI.apply[Future]() + /* + // TODO: report dotty bug. test("fold-over-selector with changed read") { val in = makeChannel[Int]() val out = makeChannel[Int]() var r0 = IndexedSeq[Int]() - val generator = go { + + // dotty bug, + val generator = async { select.fold(in){ (ch,s) => - s match { - case p:ch.read => + s.select{ + case p: ch.read => r0 = r0 :+ p out.write(p) ch.filter{ _ % p != 0 } } } } + generator.failed.foreach{ _.printStackTrace() } - go { + async { for(i <- 2 to Int.MaxValue) { in.write(i) } } - val read = go { + val read = async { for(i <- 1 to 100) yield { val x = out.read x } } - read map (r => assert(r(18)===67 && r.last === 541) ) + read map (r => assert(r(18) == 67 && r.last == 541) ) + } + */ - + /* test("fold-over-selector with swap read") { val in1 = makeChannel[Int]() val in2 = makeChannel[Int]() val quit = makeChannel[Boolean]() - val generator = go { + val generator = async { select.fold((in1,in2,0)){ case ((in1,in2,n),s) => - s match { + s select { case x:in1.read => if (x >= 100) { - select.exit((in1, in2, n)) + s.done((in1, in2, n)) } else { (in2, in1, n + x) } @@ -76,6 +87,8 @@ class FoldSelectSuite extends AsyncFunSuite generator.map(r => assert(r._3 == -50)) } + */ + } From 1a945c6d7077c438fc01272d1d217eae862bd57d Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Sun, 21 Feb 2021 19:24:28 +0200 Subject: [PATCH 037/161] adoption to 3.0.0-RC1 in progress --- build.sbt | 8 ++--- project/plugins.sbt | 4 +-- .../src/main/scala/gopher/ReadChannel.scala | 12 +++---- shared/src/main/scala/gopher/Select.scala | 14 ++++++-- .../src/main/scala/gopher/SelectForever.scala | 5 ++- .../src/main/scala/gopher/SelectGroup.scala | 6 ++-- .../main/scala/gopher/SelectListeners.scala | 2 +- shared/src/main/scala/gopher/SelectLoop.scala | 2 +- shared/src/main/scala/gopher/Time.scala | 4 +-- .../src/main/scala/gopher/WriteChannel.scala | 8 ++--- .../gopher/channels/FoldSelectSuite.scala | 34 ++++++++++++++++--- 11 files changed, 69 insertions(+), 30 deletions(-) diff --git a/build.sbt b/build.sbt index ee79d917..0d42cdd4 100644 --- a/build.sbt +++ b/build.sbt @@ -1,5 +1,5 @@ -val dottyVersion = "3.0.0-RC1-bin-SNAPSHOT" -//val dottyVersion = "3.0.0-M3" +//val dottyVersion = "3.0.0-RC2-bin-SNAPSHOT" +val dottyVersion = "3.0.0-RC1" //val dottyVersion = dottyLatestNightlyBuild.get @@ -9,8 +9,8 @@ val sharedSettings = Seq( scalaVersion := dottyVersion, name := "scala-gopher", resolvers += "Local Ivy Repository" at "file://"+Path.userHome.absolutePath+"/.ivy2/local", - libraryDependencies += "com.github.rssh" %%% "dotty-cps-async" % "0.3.6-SNAPSHOT", - libraryDependencies += "org.scalameta" %%% "munit" % "0.7.21-SNAPSHOT" % Test, + libraryDependencies += "com.github.rssh" %%% "dotty-cps-async" % "0.4.0", + libraryDependencies += "org.scalameta" %%% "munit" % "0.7.22" % Test, testFrameworks += new TestFramework("munit.Framework") ) diff --git a/project/plugins.sbt b/project/plugins.sbt index c04c0de9..26bbb96a 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,7 +1,7 @@ -addSbtPlugin("ch.epfl.lamp" % "sbt-dotty" % "0.5.1") +addSbtPlugin("ch.epfl.lamp" % "sbt-dotty" % "0.5.3") addSbtPlugin("com.jsuereth" % "sbt-pgp" % "2.0.2") addSbtPlugin("com.typesafe.sbt" % "sbt-site" % "1.4.0") addSbtPlugin("com.typesafe.sbt" % "sbt-ghpages" % "0.6.3") addSbtPlugin("org.portable-scala" % "sbt-scalajs-crossproject" % "1.0.0") -addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.4.0") +addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.5.0") diff --git a/shared/src/main/scala/gopher/ReadChannel.scala b/shared/src/main/scala/gopher/ReadChannel.scala index 32625418..c587a090 100644 --- a/shared/src/main/scala/gopher/ReadChannel.scala +++ b/shared/src/main/scala/gopher/ReadChannel.scala @@ -35,9 +35,9 @@ trait ReadChannel[F[_], A]: def aread:F[A] = asyncMonad.adoptCallbackStyle(f => addReader(SimpleReader(f))) - inline def read: A = await(aread)(using rAsyncMonad) + transparent inline def read: A = await(aread)(using rAsyncMonad) - inline def ? : A = await(aread)(using rAsyncMonad) + transparent inline def ? : A = await(aread)(using rAsyncMonad) /** * return F which contains sequence from first `n` elements. @@ -68,7 +68,7 @@ trait ReadChannel[F[_], A]: }) ) - inline def optRead: Option[A] = await(aOptRead)(using rAsyncMonad) + transparent inline def optRead: Option[A] = await(aOptRead)(using rAsyncMonad) def foreach_async(f: A=>F[Unit]): F[Unit] = given CpsAsyncMonad[F] = asyncMonad @@ -91,7 +91,7 @@ trait ReadChannel[F[_], A]: * run code each time when new object is arriced. * until end of stream is not reached **/ - inline def foreach(inline f: A=>Unit): Unit = + transparent inline def foreach(inline f: A=>Unit): Unit = await(aforeach(f))(using rAsyncMonad) def map[B](f: A=>B): ReadChannel[F,B] = @@ -128,9 +128,9 @@ trait ReadChannel[F[_], A]: s } - inline def fold[S](inline s0:S)(inline f: (S,A) => S ): S = + transparent inline def fold[S](inline s0:S)(inline f: (S,A) => S ): S = await[F,S](afold(s0)(f))(using rAsyncMonad) - + class DoneReadChannel extends ReadChannel[F,Unit]: def addReader(reader: Reader[Unit]): Unit = diff --git a/shared/src/main/scala/gopher/Select.scala b/shared/src/main/scala/gopher/Select.scala index 376f346d..cf6061e6 100644 --- a/shared/src/main/scala/gopher/Select.scala +++ b/shared/src/main/scala/gopher/Select.scala @@ -45,10 +45,14 @@ class Select[F[_]](api: Gopher[F]): } } - inline def afold[S](s0:S)(inline step: (S,SelectGroup[F,S|SelectFold.Done[S]])=> S | SelectFold.Done[S]) : F[S] = + transparent inline def afold[S](s0:S)(inline step: (S,SelectGroup[F,S|SelectFold.Done[S]])=> S | SelectFold.Done[S]) : F[S] = async[F](using api.asyncMonad).apply{ fold(s0)(step) } + + def afold_async[S](s0:S)(step: (S,SelectGroup[F,S|SelectFold.Done[S]])=> F[S | SelectFold.Done[S]]) : F[S] = + fold_async(s0)(step) + //def map[A](step: PartialFunction[SelectGroup[F,A],A|SelectFold.Done[Unit]]): ReadChannel[F,A] = @@ -77,12 +81,18 @@ class Select[F[_]](api: Gopher[F]): def forever: SelectForever[F] = new SelectForever[F](api ) - inline def aforever(inline pf: PartialFunction[Any,Unit]): F[Unit] = + transparent inline def aforever(inline pf: PartialFunction[Any,Unit]): F[Unit] = async(using api.asyncMonad).apply { val runner = new SelectForever[F](api) runner.apply(pf) } + def aforever_async(pf: PartialFunction[Any,F[Unit]]): F[Unit] = + given CpsSchedulingMonad[F] = api.asyncMonad + async(using api.asyncMonad).apply { + val runner = new SelectForever[F](api) + runner.applyAsync(pf) + } diff --git a/shared/src/main/scala/gopher/SelectForever.scala b/shared/src/main/scala/gopher/SelectForever.scala index 7c30518e..eb3d8f32 100644 --- a/shared/src/main/scala/gopher/SelectForever.scala +++ b/shared/src/main/scala/gopher/SelectForever.scala @@ -9,11 +9,14 @@ import scala.concurrent.duration._ class SelectForever[F[_]](api: Gopher[F]) extends SelectGroupBuilder[F,Unit, Unit](api): - inline def apply(inline pf: PartialFunction[Any,Unit]): Unit = + transparent inline def apply(inline pf: PartialFunction[Any,Unit]): Unit = ${ Select.foreverImpl('pf,'api) } + transparent inline def applyAsync(inline pf: PartialFunction[Any,F[Unit]]): Unit = + ??? + def runAsync(): F[Unit] = given CpsSchedulingMonad[F] = api.asyncMonad async[F]{ diff --git a/shared/src/main/scala/gopher/SelectGroup.scala b/shared/src/main/scala/gopher/SelectGroup.scala index da0a3025..cda00a26 100644 --- a/shared/src/main/scala/gopher/SelectGroup.scala +++ b/shared/src/main/scala/gopher/SelectGroup.scala @@ -32,7 +32,7 @@ class SelectGroup[F[_], S](api: Gopher[F]) extends SelectListeners[F,S,S]: val startTime = new AtomicLong(0L) var timeoutScheduled: Option[Time.Scheduled] = None - override def asyncMonad = api.asyncMonad + override def asyncMonad = api.asyncMonad def addReader[A](ch: ReadChannel[F,A], action: Try[A]=>F[S]): Unit = val record = ReaderRecord(ch, action) @@ -61,12 +61,12 @@ class SelectGroup[F[_], S](api: Gopher[F]) extends SelectListeners[F,S,S]: def runAsync():F[S] = retval - inline def apply(inline pf: PartialFunction[Any,S]): S = + transparent inline def apply(inline pf: PartialFunction[Any,S]): S = ${ Select.onceImpl[F,S]('pf, 'api ) } - inline def select(inline pf: PartialFunction[Any,S]): S = + transparent inline def select(inline pf: PartialFunction[Any,S]): S = ${ Select.onceImpl[F,S]('pf, 'api ) } diff --git a/shared/src/main/scala/gopher/SelectListeners.scala b/shared/src/main/scala/gopher/SelectListeners.scala index 81a1840c..38cbd73f 100644 --- a/shared/src/main/scala/gopher/SelectListeners.scala +++ b/shared/src/main/scala/gopher/SelectListeners.scala @@ -16,7 +16,7 @@ trait SelectListeners[F[_],S, R]: def runAsync():F[R] - inline def run(): R = await(runAsync())(using asyncMonad) + transparent inline def run(): R = await(runAsync())(using asyncMonad) diff --git a/shared/src/main/scala/gopher/SelectLoop.scala b/shared/src/main/scala/gopher/SelectLoop.scala index 854f7a6d..fb49ba50 100644 --- a/shared/src/main/scala/gopher/SelectLoop.scala +++ b/shared/src/main/scala/gopher/SelectLoop.scala @@ -8,7 +8,7 @@ import scala.concurrent.duration._ class SelectLoop[F[_]](api: Gopher[F]) extends SelectGroupBuilder[F,Boolean, Unit](api): - inline def apply(inline pf: PartialFunction[Any,Boolean]): Unit = + transparent inline def apply(inline pf: PartialFunction[Any,Boolean]): Unit = ${ Select.loopImpl[F]('pf, 'api ) } diff --git a/shared/src/main/scala/gopher/Time.scala b/shared/src/main/scala/gopher/Time.scala index 30630a93..6038c613 100644 --- a/shared/src/main/scala/gopher/Time.scala +++ b/shared/src/main/scala/gopher/Time.scala @@ -47,7 +47,7 @@ abstract class Time[F[_]](gopherAPI: Gopher[F]) { retval } - inline def sleep(duration: FiniteDuration): FiniteDuration = + transparent inline def sleep(duration: FiniteDuration): FiniteDuration = given CpsSchedulingMonad[F] = gopherAPI.asyncMonad await(asleep(duration)) @@ -134,7 +134,7 @@ object Time: def asleep[F[_]](duration: FiniteDuration)(using Gopher[F]): F[FiniteDuration] = summon[Gopher[F]].time.asleep(duration) - inline def sleep[F[_]](duration: FiniteDuration)(using Gopher[F]): FiniteDuration = + transparent inline def sleep[F[_]](duration: FiniteDuration)(using Gopher[F]): FiniteDuration = summon[Gopher[F]].time.sleep(duration) diff --git a/shared/src/main/scala/gopher/WriteChannel.scala b/shared/src/main/scala/gopher/WriteChannel.scala index 5083e28c..b222b270 100644 --- a/shared/src/main/scala/gopher/WriteChannel.scala +++ b/shared/src/main/scala/gopher/WriteChannel.scala @@ -22,13 +22,13 @@ trait WriteChannel[F[_], A]: // inline def apply(a:A): Unit = await(awrite(a))(using asyncMonad) // inline def unapply(a:A): Some[A] = ??? - inline def write(inline a:A): Unit = await(awrite(a))(using asyncMonad) + transparent inline def write(inline a:A): Unit = await(awrite(a))(using asyncMonad) @targetName("write1") - inline def <~ (inline a:A): Unit = await(awrite(a))(using asyncMonad) + transparent inline def <~ (inline a:A): Unit = await(awrite(a))(using asyncMonad) @targetName("write2") - inline def ! (inline a:A): Unit = await(awrite(a))(using asyncMonad) + transparent inline def ! (inline a:A): Unit = await(awrite(a))(using asyncMonad) //def Write(x:A):WritePattern = new WritePattern(x) @@ -50,7 +50,7 @@ trait WriteChannel[F[_], A]: } } - inline def writeAll(inline collection: IterableOnce[A]): Unit = + transparent inline def writeAll(inline collection: IterableOnce[A]): Unit = await(awriteAll(collection))(using asyncMonad) diff --git a/shared/src/test/scala/gopher/channels/FoldSelectSuite.scala b/shared/src/test/scala/gopher/channels/FoldSelectSuite.scala index 03171b6c..17ddbccf 100644 --- a/shared/src/test/scala/gopher/channels/FoldSelectSuite.scala +++ b/shared/src/test/scala/gopher/channels/FoldSelectSuite.scala @@ -17,8 +17,33 @@ class FoldSelectSuite extends FunSuite given Gopher[Future] = SharedGopherAPI.apply[Future]() - /* + // TODO: report dotty bug. + test("fold-over-selector-compile-bug with changed read") { + val in = makeChannel[Int]() + val out = makeChannel[Int]() + var r0 = IndexedSeq[Int]() + + // dotty bug, + + val generator = async { + select.fold(in){ (ch,s) => + s.select{ + case p: ch.read => + //r0 = r0 :+ p + //out.write(p) + //ch.filter{ _ % p != 0 } + ch + } + } + } + + + } + + + // TODO: report dotty bug. + /* test("fold-over-selector with changed read") { val in = makeChannel[Int]() val out = makeChannel[Int]() @@ -55,8 +80,10 @@ class FoldSelectSuite extends FunSuite } */ + + - /* + test("fold-over-selector with swap read") { val in1 = makeChannel[Int]() @@ -87,8 +114,7 @@ class FoldSelectSuite extends FunSuite generator.map(r => assert(r._3 == -50)) } - */ - + } From 21c4c2e572a459247f886cde32c809f5bbd31e89 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Wed, 24 Feb 2021 21:13:47 +0200 Subject: [PATCH 038/161] adopted to scala-3.0.0-RC1 --- shared/src/main/scala/gopher/Select.scala | 7 +++++-- .../src/test/scala/gopher/channels/FoldSelectSuite.scala | 3 ++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/shared/src/main/scala/gopher/Select.scala b/shared/src/main/scala/gopher/Select.scala index cf6061e6..7df1c65d 100644 --- a/shared/src/main/scala/gopher/Select.scala +++ b/shared/src/main/scala/gopher/Select.scala @@ -9,7 +9,7 @@ import scala.concurrent.duration._ class Select[F[_]](api: Gopher[F]): - inline def apply[A](inline pf: PartialFunction[Any,A]): A = + transparent inline def apply[A](inline pf: PartialFunction[Any,A]): A = ${ Select.onceImpl[F,A]('pf, 'api ) } @@ -99,6 +99,8 @@ class Select[F[_]](api: Gopher[F]): object Select: + import cps.forest.TransformUtil + sealed trait SelectGroupExpr[F[_],S, R]: def toExprOf[X <: SelectListeners[F,S, R]]: Expr[X] @@ -321,7 +323,8 @@ object Select: oldArgSymbol: quotes.reflect.Symbol, body: quotes.reflect.Term): quotes.reflect.Term = import quotes.reflect._ - val mt = MethodType(List(argName))(_ => List(argType), _ => body.tpe.widen) + val widenReturnType = TransformUtil.veryWiden(body.tpe) + val mt = MethodType(List(argName))(_ => List(argType), _ => widenReturnType) Lambda(Symbol.spliceOwner, mt, (owner,args) => substIdent(body,oldArgSymbol, args.head.asInstanceOf[Term], owner).changeOwner(owner)) diff --git a/shared/src/test/scala/gopher/channels/FoldSelectSuite.scala b/shared/src/test/scala/gopher/channels/FoldSelectSuite.scala index 17ddbccf..29595122 100644 --- a/shared/src/test/scala/gopher/channels/FoldSelectSuite.scala +++ b/shared/src/test/scala/gopher/channels/FoldSelectSuite.scala @@ -16,7 +16,7 @@ class FoldSelectSuite extends FunSuite import scala.concurrent.ExecutionContext.Implicits.global given Gopher[Future] = SharedGopherAPI.apply[Future]() - + /* // TODO: report dotty bug. test("fold-over-selector-compile-bug with changed read") { val in = makeChannel[Int]() @@ -39,6 +39,7 @@ class FoldSelectSuite extends FunSuite } + */ From 60670618b527297b0ad449fa405dad9c163cad76 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Sun, 7 Mar 2021 22:51:18 +0200 Subject: [PATCH 039/161] adopted for RC2-snapshot --- build.sbt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/build.sbt b/build.sbt index 0d42cdd4..4214bdef 100644 --- a/build.sbt +++ b/build.sbt @@ -1,5 +1,5 @@ -//val dottyVersion = "3.0.0-RC2-bin-SNAPSHOT" -val dottyVersion = "3.0.0-RC1" +val dottyVersion = "3.0.0-RC2-bin-SNAPSHOT" +//val dottyVersion = "3.0.0-RC1" //val dottyVersion = dottyLatestNightlyBuild.get @@ -9,8 +9,8 @@ val sharedSettings = Seq( scalaVersion := dottyVersion, name := "scala-gopher", resolvers += "Local Ivy Repository" at "file://"+Path.userHome.absolutePath+"/.ivy2/local", - libraryDependencies += "com.github.rssh" %%% "dotty-cps-async" % "0.4.0", - libraryDependencies += "org.scalameta" %%% "munit" % "0.7.22" % Test, + libraryDependencies += "com.github.rssh" %%% "dotty-cps-async" % "0.5.0-SNAPSHOT", + libraryDependencies += "org.scalameta" %%% "munit" % "0.7.22+7-1e1017a3+20210228-1512-SNAPSHOT" % Test, testFrameworks += new TestFramework("munit.Framework") ) From 659f7da0af33ccb16fad9c73f90d28860d6ee7d8 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Sun, 7 Mar 2021 22:53:03 +0200 Subject: [PATCH 040/161] revision of FilteredChannel, implemented 1-st select.fold test --- .../gopher/impl/FilteredReadChannel.scala | 35 ++++++++++---- .../gopher/channels/ChannelFilterSuite.scala | 37 +++++++++++++++ .../gopher/channels/FoldSelectSuite.scala | 40 +++------------- .../gopher/channels/MacroSelectSuite.scala | 46 +++++++++++++++++++ 4 files changed, 116 insertions(+), 42 deletions(-) create mode 100644 shared/src/test/scala/gopher/channels/ChannelFilterSuite.scala diff --git a/shared/src/main/scala/gopher/impl/FilteredReadChannel.scala b/shared/src/main/scala/gopher/impl/FilteredReadChannel.scala index 1bab01b1..868a3d2e 100644 --- a/shared/src/main/scala/gopher/impl/FilteredReadChannel.scala +++ b/shared/src/main/scala/gopher/impl/FilteredReadChannel.scala @@ -3,30 +3,41 @@ package gopher.impl import gopher._ import scala.util._ +import java.util.concurrent.ConcurrentLinkedQueue +import java.util.concurrent.atomic.AtomicBoolean class FilteredReadChannel[F[_],A](internal: ReadChannel[F,A], p: A=>Boolean) extends ReadChannel[F,A] { - + class FilteredReader(nested: Reader[A]) extends Reader[A] { - def wrappedFun(fun: (Try[A] => Unit) ): (Try[A] => Unit) = { + val markedUsed = new AtomicBoolean(false) + + def wrappedFun(fun: Try[A]=>Unit): (Try[A] => Unit) = { case Success(a) => - if (p(a)) + if p(a) then + if (markedUsed.get()) { + nested.markUsed() + } fun(Success(a)) + else + nested.markFree() + internal.addReader(this) case Failure(ex) => fun(Failure(ex)) } - override def capture(): Option[Try[A]=>Unit] = + override def capture(): Option[Try[A]=>Unit] = nested.capture().map{ fun => - wrappedFun(fun) + wrappedFun(fun) } - + override def canExpire: Boolean = nested.canExpire override def isExpired: Boolean = nested.isExpired - override def markUsed(): Unit = nested.markUsed() + override def markUsed(): Unit = + markedUsed.lazySet(true) override def markFree(): Unit = nested.markFree() @@ -47,13 +58,21 @@ class FilteredAsyncReadChannel[F[_],A](internal: ReadChannel[F,A], p: A=>F[Boole class FilteredReader(nested: Reader[A]) extends Reader[A] { + val markedUsed = new AtomicBoolean(false) + def wrappedFun(fun: (Try[A] => Unit) ): (Try[A] => Unit) = { case Success(a) => gopherApi.asyncMonad.spawn( gopherApi.asyncMonad.mapTry(p(a)){ case Success(v) => if (v) { + if (markedUsed.get()) { + nested.markUsed() + } fun(Success(a)) + } else { + nested.markFree() + internal.addReader(this) } case Failure(ex) => fun(Failure(ex)) @@ -72,7 +91,7 @@ class FilteredAsyncReadChannel[F[_],A](internal: ReadChannel[F,A], p: A=>F[Boole override def isExpired: Boolean = nested.isExpired - override def markUsed(): Unit = nested.markUsed() + override def markUsed(): Unit = markedUsed.lazySet(true) override def markFree(): Unit = nested.markFree() diff --git a/shared/src/test/scala/gopher/channels/ChannelFilterSuite.scala b/shared/src/test/scala/gopher/channels/ChannelFilterSuite.scala new file mode 100644 index 00000000..a6e9e2a9 --- /dev/null +++ b/shared/src/test/scala/gopher/channels/ChannelFilterSuite.scala @@ -0,0 +1,37 @@ +package gopher.channels + +import gopher._ +import scala.concurrent.Future +import scala.util.Try +import scala.util.Success +import scala.util.Failure + +import cps._ +import cps.monads.FutureAsyncMonad + +import munit._ + + +class ChannelFilterSuite extends FunSuite: + + import scala.concurrent.ExecutionContext.Implicits.global + given Gopher[Future] = SharedGopherAPI.apply[Future]() + + + test("odd filter should leave only odd numbers in filtered channel") { + + val ch = makeChannel[Int]() + + val filtered = ch.filter(_ % 2 == 0) + + ch.awriteAll(1 to 100) + async { + var i = 0 + while(i < 50) { + val x = filtered.read + assert( x % 2 == 0) + i=i+1 + } + } + + } \ No newline at end of file diff --git a/shared/src/test/scala/gopher/channels/FoldSelectSuite.scala b/shared/src/test/scala/gopher/channels/FoldSelectSuite.scala index 29595122..f6773df3 100644 --- a/shared/src/test/scala/gopher/channels/FoldSelectSuite.scala +++ b/shared/src/test/scala/gopher/channels/FoldSelectSuite.scala @@ -15,37 +15,11 @@ class FoldSelectSuite extends FunSuite import scala.concurrent.ExecutionContext.Implicits.global given Gopher[Future] = SharedGopherAPI.apply[Future]() - - /* - // TODO: report dotty bug. - test("fold-over-selector-compile-bug with changed read") { - val in = makeChannel[Int]() - val out = makeChannel[Int]() - var r0 = IndexedSeq[Int]() - - // dotty bug, - - val generator = async { - select.fold(in){ (ch,s) => - s.select{ - case p: ch.read => - //r0 = r0 :+ p - //out.write(p) - //ch.filter{ _ % p != 0 } - ch - } - } - } - - - } - */ - - // TODO: report dotty bug. - /* test("fold-over-selector with changed read") { + implicit val printCode = cps.macroFlags.PrintCode + val in = makeChannel[Int]() val out = makeChannel[Int]() var r0 = IndexedSeq[Int]() @@ -62,8 +36,8 @@ class FoldSelectSuite extends FunSuite } } - generator.failed.foreach{ _.printStackTrace() } - async { + //generator.failed.foreach{ _.printStackTrace() } + val writer = async { for(i <- 2 to Int.MaxValue) { in.write(i) } @@ -78,13 +52,10 @@ class FoldSelectSuite extends FunSuite read map (r => assert(r(18) == 67 && r.last == 541) ) - } - */ - - + /* test("fold-over-selector with swap read") { val in1 = makeChannel[Int]() @@ -115,6 +86,7 @@ class FoldSelectSuite extends FunSuite generator.map(r => assert(r._3 == -50)) } + */ } diff --git a/shared/src/test/scala/gopher/channels/MacroSelectSuite.scala b/shared/src/test/scala/gopher/channels/MacroSelectSuite.scala index 836c9fad..12fb48dd 100644 --- a/shared/src/test/scala/gopher/channels/MacroSelectSuite.scala +++ b/shared/src/test/scala/gopher/channels/MacroSelectSuite.scala @@ -52,6 +52,52 @@ class MacroSelectSuite extends FunSuite } + test("select operation with async-op insode ") { + + val channel = makeChannel[Int](100) + val middle = makeChannel[Int](100) + + async[Future] { + var i = 1 + while(i <= 1000) { + channel <~ i + i+=1 + } + //TODO: implement for in goas preprocessor to async + // dotty bug: position not set + //for( i <- 1 to 1000) + // channel <~ i + } + + var sum = 0 + val consumer1 = async[Future] { + select.loop{ + case i: channel.read => + //System.err.println("received:"+i) + middle.write(i) + i < 1000 + } + sum + } + + val consumer2 = async[Future] { + select.loop{ + case i: middle.read => + //System.err.println("received:"+i) + sum = sum + i + i < 1000 + } + sum + } + + for{ + _ <- consumer2 + xsum = (1 to 1000).sum + } yield assert(xsum == sum) + + } + + test("select with run-once") { val channel1 = makeChannel[Int](100) From 7444748832322dc89749d1b38d52fc2843e0d9e4 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Mon, 8 Mar 2021 06:24:58 +0200 Subject: [PATCH 041/161] enabled all tests in FoldSelectSuite --- shared/src/main/scala/gopher/impl/Expirable.scala | 2 +- shared/src/test/scala/gopher/channels/FoldSelectSuite.scala | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/shared/src/main/scala/gopher/impl/Expirable.scala b/shared/src/main/scala/gopher/impl/Expirable.scala index a9021524..6a6e626c 100644 --- a/shared/src/main/scala/gopher/impl/Expirable.scala +++ b/shared/src/main/scala/gopher/impl/Expirable.scala @@ -24,7 +24,7 @@ trait Expirable[A]: def isExpired: Boolean /** - * capture object, and after this we can or return one + * capture object, and after this we can or use one (markUsed will be called) or abandon (markFree) **/ def capture(): Option[A] diff --git a/shared/src/test/scala/gopher/channels/FoldSelectSuite.scala b/shared/src/test/scala/gopher/channels/FoldSelectSuite.scala index f6773df3..41be84c0 100644 --- a/shared/src/test/scala/gopher/channels/FoldSelectSuite.scala +++ b/shared/src/test/scala/gopher/channels/FoldSelectSuite.scala @@ -55,7 +55,7 @@ class FoldSelectSuite extends FunSuite } - /* + test("fold-over-selector with swap read") { val in1 = makeChannel[Int]() @@ -86,7 +86,7 @@ class FoldSelectSuite extends FunSuite generator.map(r => assert(r._3 == -50)) } - */ + } From 071bb7537121a7e69180d85e4d50b21ef2a8a428 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Mon, 8 Mar 2021 13:37:55 +0200 Subject: [PATCH 042/161] implemented zip --- .../src/main/scala/gopher/ReadChannel.scala | 22 +++++++- .../gopher/channels/FoldSelectSuite.scala | 2 +- .../scala/gopher/channels/InputOpsSuite.scala | 53 ++++++++++--------- 3 files changed, 50 insertions(+), 27 deletions(-) rename {0.99.x => shared}/src/test/scala/gopher/channels/InputOpsSuite.scala (91%) diff --git a/shared/src/main/scala/gopher/ReadChannel.scala b/shared/src/main/scala/gopher/ReadChannel.scala index c587a090..d985b9c0 100644 --- a/shared/src/main/scala/gopher/ReadChannel.scala +++ b/shared/src/main/scala/gopher/ReadChannel.scala @@ -100,7 +100,6 @@ trait ReadChannel[F[_], A]: def mapAsync[B](f: A=>F[B]): ReadChannel[F,B] = new MappedAsyncReadChannel(this, f) - def filter(p: A=>Boolean): ReadChannel[F,A] = new FilteredReadChannel(this,p) @@ -131,6 +130,27 @@ trait ReadChannel[F[_], A]: transparent inline def fold[S](inline s0:S)(inline f: (S,A) => S ): S = await[F,S](afold(s0)(f))(using rAsyncMonad) + def zip[B](x: ReadChannel[F,B]): ReadChannel[F,(A,B)] = + given CpsSchedulingMonad[F] = asyncMonad + val retval = gopherApi.makeChannel[(A,B)]() + asyncMonad.spawn(async[F]{ + var done = false + while(!done) { + this.optRead match + case Some(a) => + x.optRead match + case Some(b) => + retval.write((a,b)) + case None => + done=true + case None => + done = true + } + retval.close() + }) + retval + + class DoneReadChannel extends ReadChannel[F,Unit]: def addReader(reader: Reader[Unit]): Unit = diff --git a/shared/src/test/scala/gopher/channels/FoldSelectSuite.scala b/shared/src/test/scala/gopher/channels/FoldSelectSuite.scala index 41be84c0..822a6f13 100644 --- a/shared/src/test/scala/gopher/channels/FoldSelectSuite.scala +++ b/shared/src/test/scala/gopher/channels/FoldSelectSuite.scala @@ -18,7 +18,7 @@ class FoldSelectSuite extends FunSuite test("fold-over-selector with changed read") { - implicit val printCode = cps.macroFlags.PrintCode + //implicit val printCode = cps.macroFlags.PrintCode val in = makeChannel[Int]() val out = makeChannel[Int]() diff --git a/0.99.x/src/test/scala/gopher/channels/InputOpsSuite.scala b/shared/src/test/scala/gopher/channels/InputOpsSuite.scala similarity index 91% rename from 0.99.x/src/test/scala/gopher/channels/InputOpsSuite.scala rename to shared/src/test/scala/gopher/channels/InputOpsSuite.scala index 8ee59917..a1fd4c3a 100644 --- a/0.99.x/src/test/scala/gopher/channels/InputOpsSuite.scala +++ b/shared/src/test/scala/gopher/channels/InputOpsSuite.scala @@ -1,43 +1,47 @@ package gopher.channels +import cps._ import gopher._ -import org.scalatest._ -import org.scalatest.concurrent._ +import munit._ import scala.concurrent._ import scala.concurrent.duration._ import scala.language._ -class InputOpsSuite extends AsyncFunSuite { +import cps.monads.FutureAsyncMonad - override implicit def executionContext = ExecutionContext.global - test("map operation for input") { - val ch = gopherApi.makeChannel[String]() - ch.awriteAll(List("AAA","123","1234","12345")) - val mappedCh = ch map (_.reverse) - mappedCh.atake(4) map { l => - assert(l(0) == "AAA" && +class InputOpsSuite extends FunSuite { + + import scala.concurrent.ExecutionContext.Implicits.global + given Gopher[Future] = SharedGopherAPI.apply[Future]() + + test("map operation for input") { + val ch = makeChannel[String]() + ch.awriteAll(List("AAA","123","1234","12345")) + val mappedCh = ch map (_.reverse) + mappedCh.atake(4) map { l => + assert(l(0) == "AAA" && l(1) == "321" && l(2) == "4321" && l(3) == "54321") + } } - } - test("filter operation for input") { - val ch = gopherApi.makeChannel[String]() - ch.awriteAll(List("qqq", "AAA","123","1234","12345")) - val filteredCh = ch filter (_.contains("A")) - filteredCh.aread map { x => assert(x == "AAA") } - } + test("filter operation for input") { + val ch = makeChannel[String]() + ch.awriteAll(List("qqq", "AAA","123","1234","12345")) + val filteredCh = ch filter (_.contains("A")) + filteredCh.aread map { x => assert(x == "AAA") } + } + test("zip operation for two simple inputs") { - //val w = new Waiter - val ch1 = gopherApi.makeChannel[String]() + val ch1 = makeChannel[String]() ch1.awriteAll(List("qqq", "AAA","123","1234","12345")) - val ch2 = gopherApi.makeChannel[Int]() + val ch2 = makeChannel[Int]() ch2.awriteAll(List(1, 2, 3, 4, 5, 6)) val zipped = ch1 zip ch2 for{ r1 <- zipped.aread @@ -53,6 +57,7 @@ class InputOpsSuite extends AsyncFunSuite { } yield l } + /* test("zip operation from two finite channels") { val ch1 = Input.asInput(List(1,2),gopherApi) val ch2 = Input.asInput(List(1,2,3,4,5,6),gopherApi) @@ -311,15 +316,16 @@ class InputOpsSuite extends AsyncFunSuite { for(r <- f) yield assert(r==55) } +*/ } +/* class InputOpsSyncSuiteDisabled extends FunSuite with Waiters { -/* test("channel fold with async operation inside") { val ch1 = gopherApi.makeChannel[Int](10) val ch2 = gopherApi.makeChannel[Int](10) @@ -339,12 +345,9 @@ class InputOpsSyncSuiteDisabled extends FunSuite with Waiters { val r = Await.result(fs, 10 seconds) assert(r==110) } -*/ - - - def gopherApi = CommonTestObjects.gopherApi } +*/ \ No newline at end of file From baf8ff65addad83dc2766971ca45218278dba90c Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Mon, 15 Mar 2021 12:56:15 +0200 Subject: [PATCH 043/161] implemented OrReadChannel --- shared/src/main/scala/gopher/Channel.scala | 3 + shared/src/main/scala/gopher/Gopher.scala | 3 + .../src/main/scala/gopher/ReadChannel.scala | 27 +++ .../src/main/scala/gopher/SelectGroup.scala | 4 +- .../scala/gopher/impl/OrReadChannel.scala | 154 ++++++++++++++++++ .../scala/gopher/channels/InputOpsSuite.scala | 47 ++++-- .../gopher/channels/SelectGroupTest.scala | 1 - 7 files changed, 219 insertions(+), 20 deletions(-) create mode 100644 shared/src/main/scala/gopher/impl/OrReadChannel.scala diff --git a/shared/src/main/scala/gopher/Channel.scala b/shared/src/main/scala/gopher/Channel.scala index 40f6877b..54e87bd3 100644 --- a/shared/src/main/scala/gopher/Channel.scala +++ b/shared/src/main/scala/gopher/Channel.scala @@ -36,6 +36,9 @@ end Channel object Channel: + def apply[F[_],A]()(using Gopher[F]): Channel[F,A,A] = + summon[Gopher[F]].makeChannel[A]() + case class Read[F[_],A](a:A, ch:ReadChannel[F,A]|F[A]) { type Element = A } diff --git a/shared/src/main/scala/gopher/Gopher.scala b/shared/src/main/scala/gopher/Gopher.scala index e13d83ea..8276c413 100644 --- a/shared/src/main/scala/gopher/Gopher.scala +++ b/shared/src/main/scala/gopher/Gopher.scala @@ -55,3 +55,6 @@ extension [F[_],A](fa: F[A])(using g: Gopher[F]) def asChannel : ReadChannel[F,A] = futureInput(fa) +extension [F[_],A](c: IterableOnce[A])(using g: Gopher[F]) + def asReadChannel: ReadChannel[F,A] = + ReadChannel.fromIterable(c) \ No newline at end of file diff --git a/shared/src/main/scala/gopher/ReadChannel.scala b/shared/src/main/scala/gopher/ReadChannel.scala index d985b9c0..14cafd4d 100644 --- a/shared/src/main/scala/gopher/ReadChannel.scala +++ b/shared/src/main/scala/gopher/ReadChannel.scala @@ -150,6 +150,12 @@ trait ReadChannel[F[_], A]: }) retval + def or(other: ReadChannel[F,A]):ReadChannel[F,A] = + new OrReadChannel(this, other) + + def |(other: ReadChannel[F,A]):ReadChannel[F,A] = + new OrReadChannel(this,other) + class DoneReadChannel extends ReadChannel[F,Unit]: @@ -178,6 +184,27 @@ trait ReadChannel[F[_], A]: end ReadChannel +object ReadChannel: + + def fromIterable[F[_],A](c: IterableOnce[A])(using Gopher[F]): ReadChannel[F,A] = + given asyncMonad: CpsSchedulingMonad[F] = summon[Gopher[F]].asyncMonad + val retval = makeChannel[A]() + asyncMonad.spawn(async{ + val it = c.iterator + while(it.hasNext) { + val a = it.next() + retval.write(a) + } + retval.close() + }) + retval + + + def fromFuture[F[_],A](f: F[A])(using Gopher[F]): ReadChannel[F,A] = + futureInput(f) + +end ReadChannel + diff --git a/shared/src/main/scala/gopher/SelectGroup.scala b/shared/src/main/scala/gopher/SelectGroup.scala index cda00a26..bea84d2f 100644 --- a/shared/src/main/scala/gopher/SelectGroup.scala +++ b/shared/src/main/scala/gopher/SelectGroup.scala @@ -172,8 +172,8 @@ class SelectGroup[F[_], S](api: Gopher[F]) extends SelectListeners[F,S,S]: m.mapTry(action(v))(x => call(x)) ) }) - else - None + else + None diff --git a/shared/src/main/scala/gopher/impl/OrReadChannel.scala b/shared/src/main/scala/gopher/impl/OrReadChannel.scala new file mode 100644 index 00000000..f3ed1f8d --- /dev/null +++ b/shared/src/main/scala/gopher/impl/OrReadChannel.scala @@ -0,0 +1,154 @@ +package gopher.impl + +import gopher._ + +import scala.util._ +import java.util.concurrent.atomic.AtomicBoolean +import java.util.concurrent.atomic.AtomicReference + + + +/** + * Input, which combine two other inputs. + * + * can be created with '|' operator. + * + * {{{ + * val x = read(x|y) + * }}} + */ +case class OrReadChannel[F[_],A](x: ReadChannel[F,A], y: ReadChannel[F,A]) extends ReadChannel[F,A]: + + + val xClosed: AtomicBoolean = new AtomicBoolean(false) + val yClosed: AtomicBoolean = new AtomicBoolean(false) + + abstract class CommonBase[B](nested: Reader[B]) { + val inUse = new AtomicReference[ReadChannel[F,A]|Null](null) + val used = new AtomicBoolean(false) + + def intercept(readFun:Try[B]=>Unit): Try[B] => Unit + + /** + * Can be called only insed wrapper fun, + * set current inUse be closed, if n + * precondition: inUse.get !== null + * return: true, if bith x and y are closed + **/ + protected def setClosed(): Boolean = { + if (inUse.get() eq x) then + if (!xClosed.get()) then + xClosed.set(true) + return yClosed.get() + else + if !yClosed.get() then + yClosed.set(true) + return xClosed.get() + } + + protected def passToNested(v: Try[B], readFun:Try[B]=>Unit) = { + if (used.get()) then + nested.markUsed() + readFun(v) + } + + protected def passIfClosed(v: Try[B], readFun: Try[B]=>Unit): Unit = { + if (setClosed()) { + passToNested(v, readFun) + } else { + inUse.set(null) + } + } + + def capture(fromChannel: ReadChannel[F,A]): Option[Try[B]=>Unit] = + if inUse.compareAndSet(null,fromChannel) then + nested.capture() match + case Some(readFun) => Some(intercept(readFun)) + case None => inUse.set(null) + None + else + None + + def markFree(fromChannel: ReadChannel[F,A]): Unit = + if(inUse.get() eq fromChannel) then + nested.markFree() + inUse.set(null) + + def markUsed(fromChannel: ReadChannel[F,A]): Unit = + if (inUse.get() eq fromChannel) then + used.set(true) + + def isExpired(fromChannel: ReadChannel[F,A]): Boolean = + nested.isExpired + + def canExpire: Boolean = + nested.canExpire + + } + + class CommonReader(nested: Reader[A]) extends CommonBase[A](nested) { + + def intercept(readFun:Try[A]=>Unit): Try[A] => Unit = { + case r@Success(a) => + passToNested(r, readFun) + case f@Failure(ex) => + if (ex.isInstanceOf[ChannelClosedException]) { + passIfClosed(f, readFun) + } else { + passToNested(f,readFun) + } + } + + } + + class WrappedReader[B](common: CommonBase[B], owner: ReadChannel[F,A]) extends Reader[B] { + + def capture(): Option[Try[B]=>Unit] = + common.capture(owner) + + def canExpire: Boolean = common.canExpire + + def isExpired: Boolean = common.isExpired(owner) + + def markFree(): Unit = common.markFree(owner) + + def markUsed(): Unit = common.markUsed(owner) + + } + + def addReader(reader: Reader[A]): Unit = + val common = new CommonReader(reader) + addCommonReader(common,(c,ch)=>ch.addReader(WrappedReader(common,ch))) + + + class DoneCommonReader(nested: Reader[Unit]) extends CommonBase[Unit](nested): + + def intercept(nestedFun: Try[Unit]=>Unit): Try[Unit] => Unit = { + case r@Success(x) => + passIfClosed(r, nestedFun) + case r@Failure(ex) => + passToNested(r, nestedFun) + } + + + def addDoneReader(reader: Reader[Unit]): Unit = + addCommonReader(new DoneCommonReader(reader), (c,ch) => ch.addDoneReader(WrappedReader(c,ch))) + + // | is left-associative, so (x|y|z|v).gopherApi better be v.api, + def gopherApi: Gopher[F] = y.gopherApi + + override def toString() = s"(${x}|${y})" + + + def addCommonReader[C](common:C, addReaderFun: (C, ReadChannel[F,A]) => Unit): Unit = + var readerAdded = false + if !xClosed.get() then + readerAdded = true + addReaderFun(common,x) + if !yClosed.get() then + readerAdded = true + addReaderFun(common,y) + // if all closed, than we should add to any, to receive ChannelClosedException + if !readerAdded then + addReaderFun(common,y) + diff --git a/shared/src/test/scala/gopher/channels/InputOpsSuite.scala b/shared/src/test/scala/gopher/channels/InputOpsSuite.scala index a1fd4c3a..72e09847 100644 --- a/shared/src/test/scala/gopher/channels/InputOpsSuite.scala +++ b/shared/src/test/scala/gopher/channels/InputOpsSuite.scala @@ -57,37 +57,47 @@ class InputOpsSuite extends FunSuite { } yield l } - /* + test("zip operation from two finite channels") { - val ch1 = Input.asInput(List(1,2),gopherApi) - val ch2 = Input.asInput(List(1,2,3,4,5,6),gopherApi) + val ch1 = List(1,2).asReadChannel + val ch2 = List(1,2,3,4,5,6).asReadChannel val zipped = ch1 zip ch2 for{ r1 <- zipped.aread a1 = assert(r1 == (1, 1)) r2 <- zipped.aread - a2 = assert(r2 == (2,2)) - r3 <- recoverToSucceededIf[ChannelClosedException]{ zipped.aread } + a2 = assert( (r2 == (2,2)) ) + r3 <- async{ + try + zipped.read + assert(""=="exception should be called before") + catch + case ex: Throwable => + assert(ex.isInstanceOf[ChannelClosedException]) + } } yield r3 } + test("take from zip") { - val ch1 = Input.asInput(List(1,2,3,4,5),gopherApi) - val ch2 = Input.asInput(List(1,2,3,4,5,6),gopherApi) + val ch1 = List(1,2,3,4,5).asReadChannel + val ch2 = List(1,2,3,4,5,6).asReadChannel val zipped = ch1 zip ch2 for {ar <- zipped.atake(5) - _ <- assert(ar(0) == (1, 1)) - l <- assert(ar(4) == (5, 5)) + _ = assert(ar(0) == (1, 1)) + l = assert(ar(4) == (5, 5)) } yield l } - + + test("taking from iterator-input") { - val ch1 = Input.asInput(List(1,2,3,4,5),gopherApi) + val ch1 = List(1,2,3,4,5).asReadChannel for( ar <- ch1.atake(5) ) yield assert(ar(4)==5) } + test("zip with self will no dup channels, but generate (odd, even) pairs. It's a feature, not a bug") { - val ch = gopherApi.makeChannel[Int]() + val ch = makeChannel[Int]() val zipped = ch zip ch ch.awriteAll(List(1,2,3,4,5,6,7,8)) for{ r1 <- zipped.aread @@ -99,24 +109,27 @@ class InputOpsSuite extends FunSuite { } yield a3 } + test("reading from Q1|Q2") { - val ch1 = gopherApi.makeChannel[Int]() - val ch2 = gopherApi.makeChannel[Int]() + val ch1 = makeChannel[Int]() + val ch2 = makeChannel[Int]() val ar1 = (ch1 | ch2).aread ch1.awrite(1) for{ r1 <- ar1 - a1 <- assert( r1==1 ) ar2 = (ch1 | ch2).aread _ = ch2.awrite(2) r2 <- ar2 - a2 <- assert( r2==2 ) - } yield a1 + } yield { + assert( r1 == 1 ) + assert( r2 == 2) + } } + /* test("simultanuos reading from Q1|Q2") { val ch1 = gopherApi.makeChannel[Int]() diff --git a/shared/src/test/scala/gopher/channels/SelectGroupTest.scala b/shared/src/test/scala/gopher/channels/SelectGroupTest.scala index 42aaa381..57a662d5 100644 --- a/shared/src/test/scala/gopher/channels/SelectGroupTest.scala +++ b/shared/src/test/scala/gopher/channels/SelectGroupTest.scala @@ -22,7 +22,6 @@ class SelectGroupTest extends FunSuite { test( s"select group should not run few async processes in parallel ($name)" ){ - val group = new SelectGroup[Future,Unit](summon[Gopher[Future]]) val inW1 = new AtomicInteger(0) From 83afc00d87da3aa243212bd0e84b26ad8133acce Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Wed, 17 Mar 2021 13:19:26 +0200 Subject: [PATCH 044/161] ported yet one test for | --- .../scala/gopher/channels/InputOpsSuite.scala | 22 ++++++++++++++----- 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/shared/src/test/scala/gopher/channels/InputOpsSuite.scala b/shared/src/test/scala/gopher/channels/InputOpsSuite.scala index 72e09847..e143cb6a 100644 --- a/shared/src/test/scala/gopher/channels/InputOpsSuite.scala +++ b/shared/src/test/scala/gopher/channels/InputOpsSuite.scala @@ -6,7 +6,7 @@ import munit._ import scala.concurrent._ import scala.concurrent.duration._ -import scala.language._ +import scala.language.postfixOps import cps.monads.FutureAsyncMonad @@ -129,11 +129,11 @@ class InputOpsSuite extends FunSuite { } - /* + test("simultanuos reading from Q1|Q2") { - val ch1 = gopherApi.makeChannel[Int]() - val ch2 = gopherApi.makeChannel[Int]() + val ch1 = makeChannel[Int]() + val ch2 = makeChannel[Int]() val ar1 = (ch1 | ch2).aread val ar2 = (ch1 | ch2).aread @@ -148,14 +148,24 @@ class InputOpsSuite extends FunSuite { } else { assert(r2 == 1) } - r3 <- recoverToSucceededIf[TimeoutException] { - timeouted( (ch1 | ch2).aread, 300 milliseconds) + //r3 <- recoverToSucceededIf[TimeoutException] { + // timeouted( (ch1 | ch2).aread, 300 milliseconds) + //} + r3 <- async { + try { + await((ch1 | ch2).aread.withTimeout(300 milliseconds)) + } catch { + case ex: TimeoutException => + assert(true) + } } } yield r3 } + + /* test("reflexive or Q|Q") { val ch = gopherApi.makeChannel[Int]() val aw1 = ch.awrite(1) From f71e4569b6803cf851ac2c6480c096c0f6b761bd Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Sat, 20 Mar 2021 06:54:27 +0200 Subject: [PATCH 045/161] implemented channels.append --- .../src/main/scala/gopher/ReadChannel.scala | 2 + .../scala/gopher/impl/AppendReadChannel.scala | 72 +++++++++++++++++ .../scala/gopher/channels/InputOpsSuite.scala | 77 +++++++++++-------- 3 files changed, 119 insertions(+), 32 deletions(-) create mode 100644 shared/src/main/scala/gopher/impl/AppendReadChannel.scala diff --git a/shared/src/main/scala/gopher/ReadChannel.scala b/shared/src/main/scala/gopher/ReadChannel.scala index 14cafd4d..bdb3830c 100644 --- a/shared/src/main/scala/gopher/ReadChannel.scala +++ b/shared/src/main/scala/gopher/ReadChannel.scala @@ -156,6 +156,8 @@ trait ReadChannel[F[_], A]: def |(other: ReadChannel[F,A]):ReadChannel[F,A] = new OrReadChannel(this,other) + def append(other: ReadChannel[F,A]): ReadChannel[F, A] = + new AppendReadChannel(this, other) class DoneReadChannel extends ReadChannel[F,Unit]: diff --git a/shared/src/main/scala/gopher/impl/AppendReadChannel.scala b/shared/src/main/scala/gopher/impl/AppendReadChannel.scala new file mode 100644 index 00000000..8a01bd26 --- /dev/null +++ b/shared/src/main/scala/gopher/impl/AppendReadChannel.scala @@ -0,0 +1,72 @@ +package gopher.impl + +import gopher._ + +import scala.util._ +import java.util.concurrent.atomic.AtomicBoolean +import java.util.concurrent.atomic.AtomicReference + + + +/** + * Input, which reed from the first channel, and after first channel is closed - from second + * + * can be created with 'append' operator. + * + * {{{ + * val x = read(x|y) + * }}} + */ +case class AppendReadChannel[F[_],A](x: ReadChannel[F,A], y: ReadChannel[F,A]) extends ReadChannel[F,A]: + + + override def gopherApi: Gopher[F] = x.gopherApi + + val xClosed: AtomicBoolean = new AtomicBoolean(false) + + + class InterceptReader(nested: Reader[A]) extends Reader[A] { + + val inUsage = AtomicBoolean(false) + + def canExpire: Boolean = nested.canExpire + + def isExpired: Boolean = nested.isExpired + + def capture():Option[Try[A]=>Unit] = + nested.capture().map{ readFun => + { + case r@Success(a) => if (inUsage.get()) then + nested.markUsed() + readFun(r) + case r@Failure(ex) => + if (ex.isInstanceOf[ChannelClosedException]) then + xClosed.set(true) + nested.markFree() + y.addReader(nested) + else + if (inUsage.get()) then + nested.markUsed() + readFun(r) + } + } + + def markUsed(): Unit = + inUsage.set(true) + + def markFree(): Unit = + nested.markFree() + + } + + def addReader(reader: Reader[A]): Unit = + if (xClosed.get()) { + y.addReader(reader) + } else { + x.addReader(new InterceptReader(reader)) + } + + + def addDoneReader(reader: Reader[Unit]): Unit = + y.addDoneReader(reader) + diff --git a/shared/src/test/scala/gopher/channels/InputOpsSuite.scala b/shared/src/test/scala/gopher/channels/InputOpsSuite.scala index e143cb6a..b1b03c70 100644 --- a/shared/src/test/scala/gopher/channels/InputOpsSuite.scala +++ b/shared/src/test/scala/gopher/channels/InputOpsSuite.scala @@ -164,17 +164,23 @@ class InputOpsSuite extends FunSuite { } - - /* test("reflexive or Q|Q") { - val ch = gopherApi.makeChannel[Int]() + val ch = makeChannel[Int]() val aw1 = ch.awrite(1) val ar1 = (ch | ch).aread for {r1 <- ar1 _ = assert(r1 == 1) ar2 = (ch | ch).aread - r2_1 <- recoverToSucceededIf[TimeoutException] { - timeouted(ar2, 300 milliseconds) + //r2_1 <- recoverToSucceededIf[TimeoutException] { + // timeouted(ar2, 300 milliseconds) + //} + r2_1 <- async { + try { + ar2.withTimeout(300 milliseconds) + } catch { + case ex: TimeoutException => + assert(true) + } } _ = ch.awrite(3) r2 <- ar2 @@ -182,9 +188,10 @@ class InputOpsSuite extends FunSuite { } yield a } + test("two items read from Q1|Q2") { - val ch1 = gopherApi.makeChannel[Int]() - val ch2 = gopherApi.makeChannel[Int]() + val ch1 = makeChannel[Int]() + val ch2 = makeChannel[Int]() val aw1 = ch1.awrite(1) val aw2 = ch2.awrite(2) val chOr = (ch1 | ch2) @@ -195,9 +202,10 @@ class InputOpsSuite extends FunSuite { } yield assert( ((r1,r2)==(1,2)) ||((r1,r2)==(2,1)) ) } + test("atake read from Q1|Q2") { - val ch1 = gopherApi.makeChannel[Int]() - val ch2 = gopherApi.makeChannel[Int]() + val ch1 = makeChannel[Int]() + val ch2 = makeChannel[Int]() val aw1 = ch1.awriteAll(1 to 2) val aw2 = ch2.awriteAll(1 to 2) @@ -205,71 +213,74 @@ class InputOpsSuite extends FunSuite { for( r <- at) yield assert(r.nonEmpty) } + test("awrite/take ") { - val ch = gopherApi.makeChannel[Int]() + val ch = makeChannel[Int]() val aw = ch.awriteAll(1 to 100) val at = ch.atake(100) for (r <- at) yield assert(r.size == 100) } + test("Input foreach on closed stream must do nothing ") { - val ch = gopherApi.makeChannel[Int]() + val ch = makeChannel[Int]() @volatile var flg = false - val f = go { for(s <- ch) { + val f = async { for(s <- ch) { flg = true } } ch.close() f map (_ => assert(!flg)) } + test("Input foreach on stream with 'N' elements inside must run N times ") { //val w = new Waiter - val ch = gopherApi.makeChannel[Int]() + val ch = makeChannel[Int]() @volatile var count = 0 - val cf = go { for(s <- ch) { + val cf = async { for(s <- ch) { count += 1 } } val ar = ch.awriteAll(1 to 10) map (_ -> ch.close) val acf = for(c <- cf) yield assert(count == 10) - timeouted(ar.flatMap(_ => acf),10 seconds) + ar.flatMap(_ => acf).withTimeout(10 seconds) } + test("Input afold on stream with 'N' elements inside ") { - val ch = gopherApi.makeChannel[Int]() + val ch = makeChannel[Int]() val f = ch.afold(0)((s,e)=>s+1) val ar = ch.awriteAll(1 to 10) ar.onComplete{ case _ => ch.close() } for(r <- f) yield assert(r==10) } + test("forech with mapped closed stream") { - def one(i:Int):Future[Assertion] = { - val ch = gopherApi.makeChannel[Int]() + def one(i:Int):Future[Boolean] = { + val ch = makeChannel[Int]() val mapped = ch map (_ * 2) @volatile var count = 0 - val f = go { for(s <- mapped) { + val f = async{ for(s <- mapped) { // error in compiler - //assert((s % 2) == 0) - if ((s%2)!=0) { - throw new IllegalStateException("numbers in mapped channel must be odd") - } + assert((s % 2) == 0) count += 1 } } val ar = ch.awriteAll(1 to 10) map (_ => ch.close) for{ r <- f a <- ar - } yield assert(count == 10) + } yield count == 10 } Future.sequence(for(i <- 1 to 10) yield one(i)) map ( _.last ) } + test("forech with filtered closed stream") { - val ch = gopherApi.makeChannel[Int]() + val ch = makeChannel[Int]() val filtered = ch filter (_ %2 == 0) @volatile var count = 0 - val f = go { for(s <- filtered) { + val f = async { for(s <- filtered) { count += 1 } } val ar = ch.awriteAll(1 to 10) map (_ => ch.close) @@ -278,29 +289,31 @@ class InputOpsSuite extends FunSuite { } yield assert(count==5) } + test("append for finite stream") { - val ch1 = gopherApi.makeChannel[Int](10) - val ch2 = gopherApi.makeChannel[Int](10) + val ch1 = makeChannel[Int](10) + val ch2 = makeChannel[Int](10) val appended = ch1 append ch2 var sum = 0 var prev = 0 var monotonic = true - val f = go { for(s <- appended) { + val f = async { for(s <- appended) { // bug in compiler 2.11.7 //w{assert(prev < s)} //if (prev >= s) w{assert(false)} + //println(s"readed $s") if (prev >= s) monotonic=false prev = s sum += s } } - // it works, but for buffered channeld onComplete can be scheduled before. So, <= instead == - val a1 = ch1.awriteAll(1 to 10) map { _ => ch1.close(); assert(sum <= 55); } - val a2 = ch2.awriteAll((1 to 10)map(_*100))map(_ => assert(sum <= 5555)) + val a1 = ch1.awriteAll(1 to 10) map { _ => ch1.close(); } + val a2 = ch2.awriteAll((1 to 10)map(_*100)) for{ r1 <- a1 r2 <- a2} yield assert(monotonic) } + /* test("order of reading from unbuffered channel") { val ch = gopherApi.makeChannel[Int]() ch.awriteAll(List(10,12,34,43)) From bfa00c52c98bf37adb30c6969c7509a08000f455 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Sat, 20 Mar 2021 11:22:27 +0200 Subject: [PATCH 046/161] ported all InputOpsSuite from 0.99 --- .../src/main/scala/gopher/ReadChannel.scala | 3 + .../scala/gopher/channels/InputOpsSuite.scala | 75 +++++++------------ 2 files changed, 30 insertions(+), 48 deletions(-) diff --git a/shared/src/main/scala/gopher/ReadChannel.scala b/shared/src/main/scala/gopher/ReadChannel.scala index bdb3830c..f7c9e1cc 100644 --- a/shared/src/main/scala/gopher/ReadChannel.scala +++ b/shared/src/main/scala/gopher/ReadChannel.scala @@ -112,6 +112,9 @@ trait ReadChannel[F[_], A]: def afold[S](s0:S)(f: (S,A)=>S): F[S] = fold_async(s0)((s,e) => asyncMonad.pure(f(s,e))) + def afold_async[S](s0: S)(f: (S,A)=>F[S]): F[S] = + fold_async(s0)(f) + def fold_async[S](s0:S)(f: (S,A) => F[S] ): F[S] = given CpsSchedulingMonad[F] = asyncMonad async[F] { diff --git a/shared/src/test/scala/gopher/channels/InputOpsSuite.scala b/shared/src/test/scala/gopher/channels/InputOpsSuite.scala index b1b03c70..4998ddd3 100644 --- a/shared/src/test/scala/gopher/channels/InputOpsSuite.scala +++ b/shared/src/test/scala/gopher/channels/InputOpsSuite.scala @@ -313,9 +313,9 @@ class InputOpsSuite extends FunSuite { r2 <- a2} yield assert(monotonic) } - /* + test("order of reading from unbuffered channel") { - val ch = gopherApi.makeChannel[Int]() + val ch = makeChannel[Int]() ch.awriteAll(List(10,12,34,43)) for{ @@ -325,26 +325,13 @@ class InputOpsSuite extends FunSuite { r4 <- ch.aread } yield assert((r1,r2,r3,r4) == (10,12,34,43) ) - } - def gopherApi = CommonTestObjects.gopherApi - - def timeouted[T](f:Future[T],timeout:FiniteDuration):Future[T] = - { - val p = Promise[T]() - p.completeWith(f) - gopherApi.actorSystem.scheduler.scheduleOnce(timeout){ - p.tryFailure(new TimeoutException) - } - p.future - } - test("append for empty stream") { - val ch1 = gopherApi.makeChannel[Int]() - val ch2 = gopherApi.makeChannel[Int]() + val ch1 = makeChannel[Int]() + val ch2 = makeChannel[Int]() val appended = ch1 append ch2 val f = appended.atake(10).map(_.sum) ch1.close() @@ -352,38 +339,30 @@ class InputOpsSuite extends FunSuite { for(r <- f) yield assert(r==55) } -*/ - -} - -/* -class InputOpsSyncSuiteDisabled extends FunSuite with Waiters { - - - - test("channel fold with async operation inside") { - val ch1 = gopherApi.makeChannel[Int](10) - val ch2 = gopherApi.makeChannel[Int](10) - val fs = go { - val sum = ch1.fold(0){ (s,n) => - val n1 = ch2.read - //s+(n1+n2) -- stack overflow in 2.11.8 compiler. TODO: submit bug - s+(n+n1) - } - sum - } - go { - ch1.writeAll(1 to 10) - ch2.writeAll(1 to 10) - ch1.close() - } - val r = Await.result(fs, 10 seconds) - assert(r==110) - } + test("channel fold with async operation inside") { + val ch1 = makeChannel[Int](10) + val ch2 = makeChannel[Int](10) + val fs = async { + val sum = ch1.fold(0){ (s,n) => + val n1 = ch2.read + //s+(n1+n2) -- stack overflow in 2.11.8 compiler. TODO: submit bug + s+(n+n1) + } + sum + } + async { + ch1.writeAll(1 to 10) + ch2.writeAll(1 to 10) + ch1.close() + } + async { + val r = await(fs) + assert(r == 110) + } + } + +} - -} -*/ \ No newline at end of file From a174a380175ecaeac641488ea5cbffa648910f5f Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Sat, 20 Mar 2021 16:27:01 +0200 Subject: [PATCH 047/161] ported IOTimeutesSuite --- .../gopher/channels/IOTimeoutsSuite.scala | 154 ------------------ .../gopher/channels/IOTimeoutsSuite.scala | 141 ++++++++++++++++ 2 files changed, 141 insertions(+), 154 deletions(-) delete mode 100644 0.99.x/src/test/scala/gopher/channels/IOTimeoutsSuite.scala create mode 100644 shared/src/test/scala/gopher/channels/IOTimeoutsSuite.scala diff --git a/0.99.x/src/test/scala/gopher/channels/IOTimeoutsSuite.scala b/0.99.x/src/test/scala/gopher/channels/IOTimeoutsSuite.scala deleted file mode 100644 index a3af90fa..00000000 --- a/0.99.x/src/test/scala/gopher/channels/IOTimeoutsSuite.scala +++ /dev/null @@ -1,154 +0,0 @@ -package gopher.channels - -import gopher._ -import org.scalatest._ - -import scala.concurrent.duration._ -import scala.language._ - -class IOTimeoutsSuite extends AsyncFunSuite { - - test("messsaged from timeouts must be appear during reading attempt from empty channel") { - val ch = gopherApi.makeChannel[String]() - val (chReady, chTimeout) = ch.withInputTimeouts(300 milliseconds) - val f = gopherApi.select.once { - case x: chReady.read => 1 - case x: chTimeout.read => 2 - } - for(x <- f) yield assert( x == 2) - } - - - test("when we have value, we have no timeouts") { - val ch = gopherApi.makeChannel[String]() - ch.awrite("qqq") - val (chReady, chTimeout) = ch.withInputTimeouts(300 milliseconds) - val f = gopherApi.select.once { - case x: chReady.read => 1 - case x: chTimeout.read => 2 - } - for(x <- f) yield assert (x==1) - } - - - test("on input close it's timeout channel also must close") { - // want to use 'read || ec instead serialized - implicit val executionContext = scala.concurrent.ExecutionContext.Implicits.global - val ch = gopherApi.makeChannel[String](1) - for{ - _ <- ch.awrite("qqq") - (chReady, chTimeout) = ch.withInputTimeouts(300 milliseconds) - _ = ch.close() - f1 = gopherApi.select.once { - case x: chReady.read => 1 - case x: chTimeout.read => 2 - } - x <- f1 - _ <- assert(x==1) - _ <- recoverToSucceededIf[ChannelClosedException] { - chReady.aread - } - l <- recoverToSucceededIf[ChannelClosedException] { - chTimeout.aread - } - } yield l - } - - test("messsaged from timeouts must be appear during attempt to write to filled unbuffered channel") { - val ch = gopherApi.makeChannel[Int]() - val (chReady, chTimeout) = ch.withOutputTimeouts(150 milliseconds) - @volatile var count = 1 - val f = gopherApi.select.forever { - case x: chReady.write if (x==count) => - {}; - count += 1 // will newer called, since we have no reader - case t: chTimeout.read => - implicitly[FlowTermination[Unit]].doExit(count) - } - f map (_ => assert(count==1)) - } - - test("messsaged from timeouts must be appear during attempt to write to filled buffered channel") { - val ch = gopherApi.makeChannel[Int](1) - val (chReady, chTimeout) = ch.withOutputTimeouts(150 milliseconds) - @volatile var count = 1 - val f = gopherApi.select.forever { - case x: chReady.write if (x==count) => - {}; - count += 1 - case t: chTimeout.read => - implicitly[FlowTermination[Unit]].doExit(count) - } - f map { _ => assert(count==2) } - } - - test("when we have where to write -- no timeouts") { - val ch = gopherApi.makeChannel[Int](1) - val (chReady, chTimeout) = ch.withOutputTimeouts(300 milliseconds) - val f = gopherApi.select.once { - case x: chReady.write if (x==1) => 1 - case t: chTimeout.read => 2 - } - f map { r => assert(r == 1) } - } - - test("on output close it's timeout channel also must close") { - val ch = gopherApi.makeChannel[Int](1) - val (chReady, chTimeout) = ch.withOutputTimeouts(300 milliseconds) - val f1 = chReady.awrite(1) - for { - x1 <- f1 - _ <- assert(x1 == 1) - _ = ch.close() - l <- recoverToSucceededIf[ChannelClosedException] { - chReady.awrite(2) - } - } yield l - } - - test("during 'normal' processing timeouts are absent") { - val ch = gopherApi.makeChannel[Int]() - val (chInputReady, chInputTimeout) = ch.withInputTimeouts(300 milliseconds) - val (chOutputReady, chOutputTimeout) = ch.withOutputTimeouts(300 milliseconds) - @volatile var count = 0 - @volatile var count1 = 0 - @volatile var wasInputTimeout = false - @volatile var wasOutputTimeout = false - val maxCount = 100 - val fOut = gopherApi.select.forever { - case x: chOutputReady.write if (x==count) => - if (count == maxCount) { - implicitly[FlowTermination[Unit]].doExit(()) - } else { - count += 1 - } - case t: chOutputTimeout.read => - {}; - wasOutputTimeout = true - } - val fIn = gopherApi.select.forever { - case x: chInputReady.read => - count1 = x - if (x == maxCount) { - implicitly[FlowTermination[Unit]].doExit(()) - } - case t: chInputTimeout.read => - {}; - wasInputTimeout = true - } - for{ - _ <- fOut - _ <- fIn - _ = assert(count == maxCount) - _ = assert(count1 == maxCount) - } yield assert(!wasOutputTimeout && !wasInputTimeout) - } - - - - - def gopherApi = CommonTestObjects.gopherApi - - -} - diff --git a/shared/src/test/scala/gopher/channels/IOTimeoutsSuite.scala b/shared/src/test/scala/gopher/channels/IOTimeoutsSuite.scala new file mode 100644 index 00000000..fcd001b0 --- /dev/null +++ b/shared/src/test/scala/gopher/channels/IOTimeoutsSuite.scala @@ -0,0 +1,141 @@ +package gopher.channels + +import gopher._ +import cps._ +import gopher._ +import munit._ + +import scala.concurrent._ +import scala.concurrent.duration._ +import scala.language.postfixOps + +import cps.monads.FutureAsyncMonad + + +class IOTimeoutsSuite extends FunSuite { + + import scala.concurrent.ExecutionContext.Implicits.global + given Gopher[Future] = SharedGopherAPI.apply[Future]() + + test("messsaged from timeouts must be appear during reading attempt from empty channel") { + val ch = makeChannel[String]() + //val (chReady, chTimeout) = ch.withInputTimeouts(300 milliseconds) + async { + val f = select.once { + case x: ch.read => 1 + case t: Time.after if t == (300 milliseconds) => 2 + } + assert(f==2) + } + } + + + + test("when we have value, we have no timeouts") { + val ch = makeChannel[String]() + ch.awrite("qqq") + //val (chReady, chTimeout) = ch.withInputTimeouts(300 milliseconds) + async { + val x = select.once { + case x: ch.read => 1 + case t: Time.after if t == (300 milliseconds) => 2 + } + assert (x==1) + } + } + + + test("messsaged from timeouts must be appear during attempt to write to filled unbuffered channel") { + val ch = makeChannel[Int]() + //val (chReady, chTimeout) = ch.withOutputTimeouts(150 milliseconds) + async { + @volatile var count = 1 + select.loop{ + case x: ch.write if (x==count) => + count += 1 // will newer called, since we have no reader + true + case t: Time.after if t == (150 milliseconds) => + false + } + assert(count==1) + } + } + + + test("messsaged from timeouts must be appear during attempt to write to filled buffered channel") { + val ch = makeChannel[Int](1) + //val (chReady, chTimeout) = ch.withOutputTimeouts(150 milliseconds) + async{ + @volatile var count = 1 + select.loop { + case x: ch.write if (x==count) => + count += 1 + true + case t: Time.after if t == (150 milliseconds) => + false + } + assert(count==2) + } + } + + + test("when we have where to write -- no timeouts") { + val ch = makeChannel[Int](1) + //val (chReady, chTimeout) = ch.withOutputTimeouts(300 milliseconds) + async { + val x = select.once { + case x: ch.write if (x==1) => 1 + case t: Time.after if t == (150 milliseconds) => 2 + } + assert(x == 1) + } + } + + + + test("during 'normal' processing timeouts are absent") { + val ch = makeChannel[Int]() + //val (chInputReady, chInputTimeout) = ch.withInputTimeouts(300 milliseconds) + //val (chOutputReady, chOutputTimeout) = ch.withOutputTimeouts(300 milliseconds) + @volatile var count = 0 + @volatile var count1 = 0 + @volatile var wasInputTimeout = false + @volatile var wasOutputTimeout = false + val maxCount = 100 + + val fOut = async { + select.loop { + case x: ch.write if (x==count) => + if (count == maxCount) { + false + } else { + count += 1 + true + } + case t: Time.after if t == (300 milliseconds) => + wasOutputTimeout = true + true + } + } + val fIn = async { + select.loop { + case x: ch.read => + count1 = x + (x != maxCount) + case t: Time.after if t == (150 milliseconds) => + wasInputTimeout = true + true + } + } + for{ + _ <- fOut + _ <- fIn + _ = assert(count == maxCount) + _ = assert(count1 == maxCount) + } yield assert(!wasOutputTimeout && !wasInputTimeout) + } + + + +} + From cfa849045d0ac667b1eca5b1dd4563a3936189a4 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Tue, 23 Mar 2021 19:51:41 +0200 Subject: [PATCH 048/161] asyncWriter is implemented according to cps specs --- build.sbt | 2 +- .../src/main/scala/gopher/SelectGroup.scala | 17 +++-- .../main/scala/gopher/SelectListeners.scala | 19 +++-- shared/src/main/scala/gopher/SelectLoop.scala | 5 +- .../gopher/channels/IOTimeoutsSuite.scala | 4 +- .../gopher/channels/SelectErrorSuite.scala | 71 +++++++++++-------- 6 files changed, 65 insertions(+), 53 deletions(-) rename {0.99.x => shared}/src/test/scala/gopher/channels/SelectErrorSuite.scala (75%) diff --git a/build.sbt b/build.sbt index 4214bdef..9fe7dadc 100644 --- a/build.sbt +++ b/build.sbt @@ -30,7 +30,7 @@ lazy val gopher = crossProject(JSPlatform, JVMPlatform) .settings(sharedSettings) .disablePlugins(SitePlugin) .jvmSettings( - scalacOptions ++= Seq( "-unchecked", "-Ycheck:macros" ), + scalacOptions ++= Seq( "-unchecked", "-Ycheck:macros", "-uniqid", "-Xprint:types" ), ).jsSettings( libraryDependencies += ("org.scala-js" %%% "scalajs-java-logging" % "1.0.0").withDottyCompat(scalaVersion.value), // TODO: switch to ModuleES ? diff --git a/shared/src/main/scala/gopher/SelectGroup.scala b/shared/src/main/scala/gopher/SelectGroup.scala index bea84d2f..0a3d7fc6 100644 --- a/shared/src/main/scala/gopher/SelectGroup.scala +++ b/shared/src/main/scala/gopher/SelectGroup.scala @@ -119,17 +119,16 @@ class SelectGroup[F[_], S](api: Gopher[F]) extends SelectListeners[F,S,S]: this - def onWriteAsync[A](ch: WriteChannel[F,A], a:A) (f: A => F[S] ): this.type = - addWriter[A](ch,a,{ - case Success(()) => m.tryImpure(f(a)) - case Failure(ex) => m.error(ex) - }) + def onWriteAsync[A](ch: WriteChannel[F,A], a: ()=>F[A]) (f: A => F[S] ): this.type = + m.map(a()){ x => + addWriter[A](ch,x,{ + case Success(()) => m.tryImpure(f(x)) + case Failure(ex) => m.error(ex) + }) + } this + - def onWrite_async[A](ch: WriteChannel[F,A], a:A) (f: A => F[S] ): F[this.type] = - m.pure(onWriteAsync(ch,a)(f)) - - def onTimeout(t:FiniteDuration)(f: FiniteDuration => S): this.type = setTimeout(t,{ case Success(x) => m.tryPure(f(x)) diff --git a/shared/src/main/scala/gopher/SelectListeners.scala b/shared/src/main/scala/gopher/SelectListeners.scala index 38cbd73f..ae1cde64 100644 --- a/shared/src/main/scala/gopher/SelectListeners.scala +++ b/shared/src/main/scala/gopher/SelectListeners.scala @@ -24,6 +24,8 @@ trait SelectListeners[F[_],S, R]: abstract class SelectGroupBuilder[F[_],S, R](api: Gopher[F]) extends SelectListeners[F,S, R]: protected var groupBuilder: SelectGroup[F,S] => SelectGroup[F,S] = identity + + val m = api.asyncMonad def onRead[A](ch: ReadChannel[F,A])(f: A => S): this.type = groupBuilder = groupBuilder.andThen{ @@ -32,7 +34,9 @@ abstract class SelectGroupBuilder[F[_],S, R](api: Gopher[F]) extends SelectListe this def onReadAsync[A](ch: ReadChannel[F,A])(f: A => F[S]): this.type = - groupBuilder = groupBuilder.andThen( _.onReadAsync(ch)(f) ) + groupBuilder = groupBuilder.andThen( + _.onReadAsync(ch)(f) + ) this @@ -41,11 +45,11 @@ abstract class SelectGroupBuilder[F[_],S, R](api: Gopher[F]) extends SelectListe def onWrite[A](ch: WriteChannel[F,A], a: =>A)(f: A=>S): this.type = groupBuilder = groupBuilder.andThen{ - g => g.onWrite(ch,a)(f) + g => g.onWrite(ch,a)(f) } this - def onWriteAsync[A](ch: WriteChannel[F,A], a: =>A)(f: A=>F[S]): this.type = + def onWriteAsync[A](ch: WriteChannel[F,A], a: ()=>F[A])(f: A=>F[S]): this.type = groupBuilder = groupBuilder.andThen{ g => g.onWriteAsync(ch,a)(f) } @@ -64,17 +68,10 @@ abstract class SelectGroupBuilder[F[_],S, R](api: Gopher[F]) extends SelectListe def onTimeoutAsync(t: FiniteDuration)(f: FiniteDuration => F[S]): this.type = groupBuilder = groupBuilder.andThen{ - g => g.onTimeoutAsync(t)(f) + g => g.onTimeoutAsync(t)(f) } this def asyncMonad: CpsSchedulingMonad[F] = api.asyncMonad - - - - - - - diff --git a/shared/src/main/scala/gopher/SelectLoop.scala b/shared/src/main/scala/gopher/SelectLoop.scala index fb49ba50..c60d251f 100644 --- a/shared/src/main/scala/gopher/SelectLoop.scala +++ b/shared/src/main/scala/gopher/SelectLoop.scala @@ -14,11 +14,12 @@ class SelectLoop[F[_]](api: Gopher[F]) extends SelectGroupBuilder[F,Boolean, Uni } def runAsync(): F[Unit] = - given CpsSchedulingMonad[F] = api.asyncMonad + given m: CpsSchedulingMonad[F] = api.asyncMonad async[F]{ while{ val group = api.select.group[Boolean] - val r = groupBuilder(group).run() + val build = groupBuilder(group) + val r = build.run() r } do () } diff --git a/shared/src/test/scala/gopher/channels/IOTimeoutsSuite.scala b/shared/src/test/scala/gopher/channels/IOTimeoutsSuite.scala index fcd001b0..18a9dcf8 100644 --- a/shared/src/test/scala/gopher/channels/IOTimeoutsSuite.scala +++ b/shared/src/test/scala/gopher/channels/IOTimeoutsSuite.scala @@ -94,6 +94,9 @@ class IOTimeoutsSuite extends FunSuite { test("during 'normal' processing timeouts are absent") { + + implicit val printCode = cps.macroFlags.PrintCode + val ch = makeChannel[Int]() //val (chInputReady, chInputTimeout) = ch.withInputTimeouts(300 milliseconds) //val (chOutputReady, chOutputTimeout) = ch.withOutputTimeouts(300 milliseconds) @@ -136,6 +139,5 @@ class IOTimeoutsSuite extends FunSuite { } - } diff --git a/0.99.x/src/test/scala/gopher/channels/SelectErrorSuite.scala b/shared/src/test/scala/gopher/channels/SelectErrorSuite.scala similarity index 75% rename from 0.99.x/src/test/scala/gopher/channels/SelectErrorSuite.scala rename to shared/src/test/scala/gopher/channels/SelectErrorSuite.scala index 7a65b7bb..e5aa9796 100644 --- a/0.99.x/src/test/scala/gopher/channels/SelectErrorSuite.scala +++ b/shared/src/test/scala/gopher/channels/SelectErrorSuite.scala @@ -1,56 +1,68 @@ package gopher.channels +import cps._ import gopher._ -import gopher.channels._ -import gopher.tags._ +import munit._ -import org.scalatest._ - -import scala.language._ +import scala.language.postfixOps import scala.concurrent._ import scala.concurrent.duration._ +import cps.monads.FutureAsyncMonad + + class SelectErrorSuite extends FunSuite { import scala.concurrent.ExecutionContext.Implicits.global - + given Gopher[Future] = SharedGopherAPI.apply[Future]() - +/* test("select error handling for foreach") { - import gopherApi._ val channel = makeChannel[Int](100) var svEx: Throwable = null - val g = go { - var nWrites = 0 - var nErrors = 0 - for (s <- select.forever) { - s match { - case x: channel.write if (x == nWrites) => - nWrites = nWrites + 1 - if (nWrites == 50) { - throw new RuntimeException("Be-be-be") - } - if (nWrites == 100) { - select.exit(()) - } - case ex: select.error => - { }; svEx = ex // macro-system errors: assignments accepts as default argument - } - } - } + + var nWrites = 0 + var nErrors = 0 + + //implicit val printCode = cps.macroFlags.PrintCode + //implicit val debugLevel = cps.macroFlags.DebugLevel(10) - val tf = channel.atake(60) - Await.ready(tf, 10 seconds) + async{ + //try { + + select.loop{ + case x: channel.write if x == nWrites => + nWrites = nWrites + 1 + if (nWrites == 50) then + throw new RuntimeException("Be-be-be") + (nWrites != 100) + // case t: Time.after if t == (100 milliseconds) => + // false + } + //} catch { + // case ex: RuntimeException => + // svEx = ex + //} + + } - assert(svEx.getMessage == "Be-be-be") + /* + async { + val tf = channel.atake(50) + await(g) + assert(svEx.getMessage == "Be-be-be") + } + */ } +*/ +/* test("select error handling for once") { import gopherApi._ val channel = makeChannel[Int](100) @@ -158,5 +170,6 @@ class SelectErrorSuite extends FunSuite lazy val gopherApi = CommonTestObjects.gopherApi + */ } From 2643ce9e1e19f585105fbbef9a8f592829df565f Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Tue, 23 Mar 2021 19:57:38 +0200 Subject: [PATCH 049/161] started to port SelectErrorSuite --- .../gopher/channels/IOTimeoutsSuite.scala | 2 +- .../gopher/channels/SelectErrorSuite.scala | 25 +++++++++---------- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/shared/src/test/scala/gopher/channels/IOTimeoutsSuite.scala b/shared/src/test/scala/gopher/channels/IOTimeoutsSuite.scala index 18a9dcf8..3e6e6944 100644 --- a/shared/src/test/scala/gopher/channels/IOTimeoutsSuite.scala +++ b/shared/src/test/scala/gopher/channels/IOTimeoutsSuite.scala @@ -95,7 +95,7 @@ class IOTimeoutsSuite extends FunSuite { test("during 'normal' processing timeouts are absent") { - implicit val printCode = cps.macroFlags.PrintCode + //implicit val printCode = cps.macroFlags.PrintCode val ch = makeChannel[Int]() //val (chInputReady, chInputTimeout) = ch.withInputTimeouts(300 milliseconds) diff --git a/shared/src/test/scala/gopher/channels/SelectErrorSuite.scala b/shared/src/test/scala/gopher/channels/SelectErrorSuite.scala index e5aa9796..81c864e4 100644 --- a/shared/src/test/scala/gopher/channels/SelectErrorSuite.scala +++ b/shared/src/test/scala/gopher/channels/SelectErrorSuite.scala @@ -17,7 +17,7 @@ class SelectErrorSuite extends FunSuite import scala.concurrent.ExecutionContext.Implicits.global given Gopher[Future] = SharedGopherAPI.apply[Future]() -/* + test("select error handling for foreach") { val channel = makeChannel[Int](100) @@ -31,8 +31,8 @@ class SelectErrorSuite extends FunSuite //implicit val debugLevel = cps.macroFlags.DebugLevel(10) - async{ - //try { + val g = async{ + try { select.loop{ case x: channel.write if x == nWrites => @@ -40,27 +40,26 @@ class SelectErrorSuite extends FunSuite if (nWrites == 50) then throw new RuntimeException("Be-be-be") (nWrites != 100) - // case t: Time.after if t == (100 milliseconds) => - // false + case t: Time.after if t == (100 milliseconds) => + false } - //} catch { - // case ex: RuntimeException => - // svEx = ex - //} - + } catch { + case ex: RuntimeException => + svEx = ex + } } - /* + async { val tf = channel.atake(50) await(g) assert(svEx.getMessage == "Be-be-be") } - */ + } -*/ + /* test("select error handling for once") { From 02b579a18dd58baba0be3e1bee43bcc65eb736d9 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Wed, 24 Mar 2021 14:42:03 +0200 Subject: [PATCH 050/161] ported all tests in SelectErrorSuite --- shared/src/main/scala/gopher/Select.scala | 4 +- .../gopher/channels/SelectErrorSuite.scala | 136 ++++++++++-------- 2 files changed, 78 insertions(+), 62 deletions(-) diff --git a/shared/src/main/scala/gopher/Select.scala b/shared/src/main/scala/gopher/Select.scala index 7df1c65d..f6054528 100644 --- a/shared/src/main/scala/gopher/Select.scala +++ b/shared/src/main/scala/gopher/Select.scala @@ -206,7 +206,7 @@ object Select: def handleWrite(bind: Bind, valName: String, channel:Term, tp:TypeRepr): SelectorCaseExpr[F,S,R] = val writeFun = makeLambda(valName,tp, bind.symbol, caseDef.rhs) val e = caseDefGuard.getOrElse(valName, - reportError(s"not found binding ${valName} in write condition", caseDef.pattern.asExpr) + reportError(s"not found binding ${valName} in write condition", channel.asExpr) ) if (channel.tpe <:< TypeRepr.of[WriteChannel[F,?]]) then tp.asType match @@ -324,7 +324,7 @@ object Select: body: quotes.reflect.Term): quotes.reflect.Term = import quotes.reflect._ val widenReturnType = TransformUtil.veryWiden(body.tpe) - val mt = MethodType(List(argName))(_ => List(argType), _ => widenReturnType) + val mt = MethodType(List(argName))(_ => List(argType.widen), _ => widenReturnType) Lambda(Symbol.spliceOwner, mt, (owner,args) => substIdent(body,oldArgSymbol, args.head.asInstanceOf[Term], owner).changeOwner(owner)) diff --git a/shared/src/test/scala/gopher/channels/SelectErrorSuite.scala b/shared/src/test/scala/gopher/channels/SelectErrorSuite.scala index 81c864e4..c7147c39 100644 --- a/shared/src/test/scala/gopher/channels/SelectErrorSuite.scala +++ b/shared/src/test/scala/gopher/channels/SelectErrorSuite.scala @@ -14,11 +14,11 @@ import cps.monads.FutureAsyncMonad class SelectErrorSuite extends FunSuite { - import scala.concurrent.ExecutionContext.Implicits.global - given Gopher[Future] = SharedGopherAPI.apply[Future]() + import scala.concurrent.ExecutionContext.Implicits.global + given Gopher[Future] = SharedGopherAPI.apply[Future]() - test("select error handling for foreach") { + test("select error handling for foreach") { val channel = makeChannel[Int](100) var svEx: Throwable = null @@ -57,97 +57,118 @@ class SelectErrorSuite extends FunSuite } - } - + } -/* test("select error handling for once") { - import gopherApi._ val channel = makeChannel[Int](100) var svEx: Throwable = null - val x = 1 + - val g = go { - for (s <- select.once) { - s match { - case x: channel.write => + val g = async { + try { + select.once { + case x: channel.write if (x==1) => throw new RuntimeException("Be-be-be") - case ex: select.error => - { }; svEx = ex // macro-system errors: assignments accepts as default argument - 3 - } + 2 + //case ex: select.error => + //{ }; svEx = ex // macro-system errors: assignments accepts as default argument + // 3 + } + } catch { + case ex: RuntimeException => + svEx = ex + 3 } } - val r = Await.result(g, 10 seconds) - - assert(svEx.getMessage == "Be-be-be") - - assert(r === 3) - + async { + val r = await(g) + assert(svEx.getMessage == "Be-be-be") + assert(r == 3) + } } + test("select error handling for input") { - import gopherApi._ val channel = makeChannel[Int](100) var svEx: Throwable = null - - val out = select.map { - case x: channel.read => - if (x==55) { - throw new RuntimeException("Be-be-be") + + async { + + val out = select.map { s => + var wasError = false + s.apply{ + case x: channel.read => + try { + if (x==55) { + throw new RuntimeException("Be-be-be") + } + } catch { + case ex: RuntimeException => + wasError = true + svEx = ex } - x - case ex: select.error => - {}; svEx = ex - 56 - } + //case ex: select.error => + // {}; svEx = ex + // 56 + if (wasError) then + 56 + else + x + } + } + + channel.awriteAll(1 to 100) - channel.awriteAll(1 to 100) + val g = out.atake(80) - val g = out.atake(80) + val r = await(g) - val r = Await.result(g, 10 seconds) + assert(svEx.getMessage == "Be-be-be") - assert(svEx.getMessage == "Be-be-be") + assert(r.filter(_ == 56).size == 2) - assert(r.filter(_ == 56).size == 2) + } } + test("select error handling for fold") { - import gopherApi._ val ch1 = makeChannel[Int]() val ch2 = makeChannel[Int]() val ch3 = makeChannel[Int]() var svEx: Throwable = null - val g = select.afold((ch1,ch2,0,List[Int]())) { case ((x,y,z,l),s) => - s match { - case z1: ch3.read => + val g = + select.afold((ch1,ch2,0,List[Int]())) { case ((x,y,z,l),s) => + try { + s.apply{ + case z1: ch3.read => if (z1==10) { throw new RuntimeException("Be-be-be!") } (x,y,z1,z1::l) - case a:x.read => + case a:x.read => if (z > 20) { throw new RuntimeException("Be-be-be-1") } (y,x,z+a,z::l) - case b:y.read => + case b:y.read => (y,x,z+100*b,z::l) - case ex: select.error => - {}; svEx = ex - if (z > 20) { - select.exit((x,y,z,z::l)) - } else - (x,y,z,l) - + } + }catch{ + case ex: RuntimeException => + svEx = ex + if (z > 20) { + SelectFold.Done((x,y,z,z::l)) + } else { + (x,y,z,l) + } } } @@ -155,20 +176,15 @@ class SelectErrorSuite extends FunSuite x => ch2.awriteAll(1 to 5) } - val r = Await.result(g, 5 seconds) + async { - assert(svEx.getMessage=="Be-be-be-1") + val r =await(g) + assert(svEx.getMessage=="Be-be-be-1") - // System.err.println(s"received: ${r._4.reverse}") + } } - - - - lazy val gopherApi = CommonTestObjects.gopherApi - */ - } From 210bb0163f70091bd0dac259ee29b806aa7f913a Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Wed, 24 Mar 2021 15:25:34 +0200 Subject: [PATCH 051/161] Select.Timeout tests are in progress --- .../gopher/channels/SelectTimeoutSuite.scala | 52 +++++++++++-------- 1 file changed, 30 insertions(+), 22 deletions(-) rename {0.99.x => shared}/src/test/scala/gopher/channels/SelectTimeoutSuite.scala (77%) diff --git a/0.99.x/src/test/scala/gopher/channels/SelectTimeoutSuite.scala b/shared/src/test/scala/gopher/channels/SelectTimeoutSuite.scala similarity index 77% rename from 0.99.x/src/test/scala/gopher/channels/SelectTimeoutSuite.scala rename to shared/src/test/scala/gopher/channels/SelectTimeoutSuite.scala index 21cae005..0cda5e1d 100644 --- a/0.99.x/src/test/scala/gopher/channels/SelectTimeoutSuite.scala +++ b/shared/src/test/scala/gopher/channels/SelectTimeoutSuite.scala @@ -1,55 +1,62 @@ package gopher.channels +import cps._ import gopher._ -import gopher.channels._ -import gopher.tags._ +import munit._ -import org.scalatest._ - -import scala.language._ +import scala.language.postfixOps import scala.concurrent._ import scala.concurrent.duration._ -import akka.util.Timeout +import cps.monads.FutureAsyncMonad class SelectTimeoutSuite extends FunSuite { import scala.concurrent.ExecutionContext.Implicits.global + given Gopher[Future] = SharedGopherAPI.apply[Future]() + test("select with constant timeout which not fire") { - //pending - import gopherApi._ - val ch1 = makeChannel[Int](10) - val r = select.amap { - case x:ch1.read => + async { + val ch1 = makeChannel[Int](10) + val r = select.map{ s => + s.apply{ + case x:ch1.read => //System.err.println(s"readed ${x}") x - case y:select.timeout if (y==500.milliseconds) => + case y: Time.after if (y==500.milliseconds) => //System.err.println(s"timeout ${y}") -1 + } + } + val f1 = ch1.awrite(1) + val x = r.read + assert(x==1) } - val f1 = ch1.awrite(1) - val x = Await.result(r.aread, 10 seconds) - assert(x==1) } + test("select with constant timeout which fire") { - import gopherApi._ - val ch1 = makeChannel[Int](10) - val r = select.amap { - case x:ch1.read => + async { + val ch1 = makeChannel[Int](10) + val r = select.map{ s => + s.apply{ + case x:ch1.read => //System.err.println(s"readed ${x}") x - case x:select.timeout if (x==500.milliseconds) => + case x:Time.after if (x==500.milliseconds) => //System.err.println(s"timeout ${x}") -1 + } + } + val x = r.read + assert(x == -1) } - val x = Await.result(r.aread, 10 seconds) - assert(x == -1) } + /* test("timeout in select.forever") { import gopherApi._ val ch1 = makeChannel[Int](10) @@ -108,5 +115,6 @@ class SelectTimeoutSuite extends FunSuite } lazy val gopherApi = CommonTestObjects.gopherApi + */ } From 121f6b678c17672e32e4bbf05c4de32dc3d4b9f2 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Wed, 24 Mar 2021 16:48:07 +0200 Subject: [PATCH 052/161] ported SelectTimeputSuite --- .../gopher/channels/SelectTimeoutSuite.scala | 84 +++++++++---------- 1 file changed, 41 insertions(+), 43 deletions(-) diff --git a/shared/src/test/scala/gopher/channels/SelectTimeoutSuite.scala b/shared/src/test/scala/gopher/channels/SelectTimeoutSuite.scala index 0cda5e1d..f7624130 100644 --- a/shared/src/test/scala/gopher/channels/SelectTimeoutSuite.scala +++ b/shared/src/test/scala/gopher/channels/SelectTimeoutSuite.scala @@ -56,65 +56,63 @@ class SelectTimeoutSuite extends FunSuite } } - /* - test("timeout in select.forever") { - import gopherApi._ - val ch1 = makeChannel[Int](10) - val ch2 = makeChannel[Int]() - val chS = makeChannel[String](10) - var s = 0 - implicit val timeout = Timeout(100 milliseconds) - val f = select.forever{ - case x: ch1.read => + + test("timeout in select.loop") { + async { + val ch1 = makeChannel[Int](10) + val ch2 = makeChannel[Int]() + val chS = makeChannel[String](10) + var s = 0 + select.loop{ + case x: ch1.read => chS.write("1") - case x: ch2.read => - chS.write("2") - case x:select.timeout => + true + case x: ch2.read => + chS.write("2") + true + case x: Time.after if x == (100 millis) => s += 1 chS.write("t") - if (s > 2) select.exit(()) - } - val x = Await.result(f, 10 seconds) - assert(s > 2) + (! (s > 2) ) + } + assert(s > 2) + } } + test("timeout in select.fold") { - import gopherApi._ - val ch1 = makeChannel[Int](10) - val f = select.afold(0) { (state,sl) => - sl match { - case x: ch1.read => state+1 - case x: select.timeout if (x == 100.milliseconds) => - select.exit(state+10) + val ch1 = makeChannel[Int](10) + val f = async { + select.fold(0) { (state,sl) => + sl.apply{ + case x: ch1.read => state+1 + case x: Time.after if (x == 100.milliseconds) => + SelectFold.Done((state+10)) + } } - } - ch1.awrite(1) - val x = Await.result(f, 10 seconds) - assert(x==11) + } + ch1.awrite(1) + async { + val x = await(f) + assert(x==11) + } } + test("timeout in select.once") { - import gopherApi._ - implicit val timeout = Timeout(100 milliseconds) val ch1 = makeChannel[Int](10) var x = 0 - val f = go { - for(s <- select.once) { - s match { - case y: ch1.read => info("ch1 readed") + async { + select.once{ + case y: ch1.read => //println("ch1 readed") x=1 - case y: select.timeout => - info("ch2 readed") - x=10 - } + case y: Time.after if y == (100 milliseconds) => + //println("ch2 readed") + x=10 } + assert(x==10) } - Await.ready(f, 10 seconds) - assert(x==10) - } - lazy val gopherApi = CommonTestObjects.gopherApi - */ } From 0aea6fa936603c59913a6dc0eaadbc8bf13d5613 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Wed, 24 Mar 2021 18:29:30 +0200 Subject: [PATCH 053/161] ported unbuffer3d-select-suite --- .../channels/UnbufferedSelectSuite.scala | 86 +++++++++++-------- 1 file changed, 49 insertions(+), 37 deletions(-) rename {0.99.x => shared}/src/test/scala/gopher/channels/UnbufferedSelectSuite.scala (50%) diff --git a/0.99.x/src/test/scala/gopher/channels/UnbufferedSelectSuite.scala b/shared/src/test/scala/gopher/channels/UnbufferedSelectSuite.scala similarity index 50% rename from 0.99.x/src/test/scala/gopher/channels/UnbufferedSelectSuite.scala rename to shared/src/test/scala/gopher/channels/UnbufferedSelectSuite.scala index f5c65c08..a25ed7b9 100644 --- a/0.99.x/src/test/scala/gopher/channels/UnbufferedSelectSuite.scala +++ b/shared/src/test/scala/gopher/channels/UnbufferedSelectSuite.scala @@ -1,26 +1,24 @@ package gopher.channels +import cps._ import gopher._ -import gopher.channels._ -import gopher.tags._ +import munit._ - -import scala.language._ +import scala.language.postfixOps import scala.concurrent._ import scala.concurrent.duration._ -import org.scalatest._ -import org.scalatest.concurrent._ -class UnbufferedSelectSuite extends FunSuite with Waiters +class UnbufferedSelectSuite extends FunSuite { + import cps.monads.FutureAsyncMonad import scala.concurrent.ExecutionContext.Implicits.global + given Gopher[Future] = SharedGopherAPI.apply[Future]() + test("write without read must block ") { - import gopherApi._ - for(i <- 0 until 100) { val channel1 = makeChannel[Int](0) val w1 = channel1.awrite(1) @@ -28,44 +26,46 @@ class UnbufferedSelectSuite extends FunSuite with Waiters val r1 = channel1.aread - Await.ready(w1, 10 seconds) - Await.ready(r1, 10 seconds) - - assert(w1.isCompleted) - assert(r1.isCompleted) - - val rd = Await.result(r1, 10 seconds) - assert(rd==1) - } + async { + await(w1) + await(r1) + val rd = await(r1) + assert(rd==1) + } } + + test("fold over selector with one-direction flow") { - import gopherApi._ - for(i <- 1 to 100) { + val ch = makeChannel[Int](0) val quit = Promise[Boolean]() - val r = select.afold(0){ (x,s) => - s match { + val quitChannel = quit.future.asChannel + val r = async { + select.fold(0){ (x,s) => + s.apply{ case a:ch.read => x+a - case q:Boolean if (q==quit.future.read) => CurrentFlowTermination.exit(x) + case q: quitChannel.read => SelectFold.Done(x) } - } + } + } ch.awriteAll(1 to 10) onComplete { _ => quit success true } - val sum = Await.result(r, 3 second) - assert(sum==(1 to 10).sum) - } + async { + val sum = await(r) + assert(sum==(1 to 10).sum) + } } + test("append for finite unbuffered stream") { - val w = new Waiter - val ch1 = gopherApi.makeChannel[Int](0) - val ch2 = gopherApi.makeChannel[Int](0) + val ch1 = makeChannel[Int](0) + val ch2 = makeChannel[Int](0) val appended = ch1 append ch2 var sum = 0 var prev = 0 var monotonic = true - val f = go { for(s <- appended) { + val f = async { for(s <- appended) { // bug in compiler 2.11.7 //w{assert(prev < s)} //if (prev >= s) w{assert(false)} @@ -76,13 +76,25 @@ class UnbufferedSelectSuite extends FunSuite with Waiters val a1 = ch1.awriteAll(1 to 10) val a2 = ch2.awriteAll((1 to 10)map(_*100)) // it works, but for buffered channeld onComplete can be scheduled before. So, <= instead == - a1.onComplete{ case _ => { w{assert(sum == 55)}; ch1.close(); w.dismiss() } } - a2.onComplete{ case _ => { w{assert(sum == 5555)}; w{assert(monotonic)}; w.dismiss() } } - w.await(timeout(10 seconds), dismissals(2)) - assert(sum==5555) - assert(monotonic) + + async { + await(a1) + while (sum < 55) { + Time.sleep(50 milliseconds) + } + assert(sum == 55) + + // after this - read from 2 + ch1.close() + + await(a2) + while(sum < 5555) { + Time.sleep(50 milliseconds) + } + assert(sum == 5555) + assert(monotonic) + } } - lazy val gopherApi = CommonTestObjects.gopherApi } From b9f31cf03108244317e89464f163ba6e66eea2f7 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Thu, 25 Mar 2021 13:39:05 +0200 Subject: [PATCH 054/161] ported ReadCoroutinesSuite t new gopher --- .../gopher/channels/ReadCoroutinesSuite.scala | 25 +++++++++++-------- 1 file changed, 14 insertions(+), 11 deletions(-) rename {0.99.x => shared}/src/test/scala/gopher/channels/ReadCoroutinesSuite.scala (77%) diff --git a/0.99.x/src/test/scala/gopher/channels/ReadCoroutinesSuite.scala b/shared/src/test/scala/gopher/channels/ReadCoroutinesSuite.scala similarity index 77% rename from 0.99.x/src/test/scala/gopher/channels/ReadCoroutinesSuite.scala rename to shared/src/test/scala/gopher/channels/ReadCoroutinesSuite.scala index ea4f848f..824c9784 100644 --- a/0.99.x/src/test/scala/gopher/channels/ReadCoroutinesSuite.scala +++ b/shared/src/test/scala/gopher/channels/ReadCoroutinesSuite.scala @@ -1,12 +1,15 @@ package gopher.channels import gopher._ -import org.scalatest._ +import cps._ +import munit._ import scala.concurrent.ExecutionContext.Implicits.global -import scala.concurrent._ +import scala.concurrent.{Channel=>_,_} import scala.concurrent.duration._ +import cps.monads.FutureAsyncMonad + /* * Go analog: * @@ -35,21 +38,22 @@ import scala.concurrent.duration._ * */ object ReadCoroutines { - - lazy val integers:InputOutput[Int,Int] = - { - val y = gopherApi.makeChannel[Int]() + + given Gopher[Future] = SharedGopherAPI.apply[Future]() + + lazy val integers:Channel[Future,Int,Int] = { + val y = makeChannel[Int]() @volatile var count = 0 - go { + async { while(true) { - y <~ count + y.write(count) count = count + 1; } } y } - def gopherApi = CommonTestObjects.gopherApi + } @@ -61,7 +65,7 @@ class ReadCoroutinesSuite extends FunSuite { import language.postfixOps test("get few numbers from generarator") { - val p = go { + val p = async { val x0 = (integers ?) assert(x0 == 0) val x1 = (integers ?) @@ -69,7 +73,6 @@ class ReadCoroutinesSuite extends FunSuite { val x2 = (integers ?) assert(x2 == 2) } - Await.ready(p, 10 seconds) } From 5da88822db0236b2b235c205042f80c94d217a20 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Thu, 25 Mar 2021 14:42:05 +0200 Subject: [PATCH 055/161] changed read() to be called with parentcases --- .../scala/gopher/impl/RingBufferTest.scala | 2 +- .../test/scala/gopher/ApiAccessTests.scala | 4 +- .../src/main/scala/gopher/ReadChannel.scala | 20 +++---- .../gopher/impl/ChFlatMappedReadChannel.scala | 4 +- .../gopher/channels/AsyncChannelTests.scala | 2 +- .../gopher/channels/ChannelCloseSuite.scala | 10 ++-- .../gopher/channels/ChannelFilterSuite.scala | 2 +- .../gopher/channels/DuppedChannelsSuite.scala | 8 +-- .../gopher/channels/ExpireChannelSuite.scala | 10 ++-- .../channels/FibonnachySimpleTests.scala | 2 +- .../gopher/channels/FoldSelectSuite.scala | 2 +- .../scala/gopher/channels/InputOpsSuite.scala | 52 +++++++++---------- .../gopher/channels/MacroSelectSuite.scala | 6 +-- .../scala/gopher/channels/SelectSuite.scala | 2 +- .../gopher/channels/SelectTimeoutSuite.scala | 4 +- .../channels/UnbufferedSelectSuite.scala | 2 +- .../test/scala/hofasync/TestSharedMin.scala | 2 +- 17 files changed, 67 insertions(+), 67 deletions(-) diff --git a/js/src/test/scala/gopher/impl/RingBufferTest.scala b/js/src/test/scala/gopher/impl/RingBufferTest.scala index ab04eb85..0f85b5a1 100644 --- a/js/src/test/scala/gopher/impl/RingBufferTest.scala +++ b/js/src/test/scala/gopher/impl/RingBufferTest.scala @@ -26,7 +26,7 @@ class RingBufferTests extends munit.FunSuite{ // we should be blocked before sending next JSExecutionContext.queue.execute{ () => x = 1 - ch.aread + ch.aread() } ch.write(4) assert(x != 0) diff --git a/jvm/src/test/scala/gopher/ApiAccessTests.scala b/jvm/src/test/scala/gopher/ApiAccessTests.scala index f5c52fa0..2dec2c9a 100644 --- a/jvm/src/test/scala/gopher/ApiAccessTests.scala +++ b/jvm/src/test/scala/gopher/ApiAccessTests.scala @@ -19,7 +19,7 @@ class ApiAccessTests extends FunSuite { val ch = makeChannel[Int](0) val fw1 = ch.awrite(1) //println("after awrite") - val fr1 = ch.aread + val fr1 = ch.aread() //println("after aread, waiting result") val r1 = Await.result(fr1, 1 second) assert( r1 == 1 ) @@ -29,7 +29,7 @@ class ApiAccessTests extends FunSuite { given Gopher[Future] = JVMGopher[Future]() val ch = makeChannel[Int](1) val fw1 = ch.awrite(1) - val fr1 = ch.aread + val fr1 = ch.aread() val r1 = Await.result(fr1, 1 second) assert( r1 == 1 ) } diff --git a/shared/src/main/scala/gopher/ReadChannel.scala b/shared/src/main/scala/gopher/ReadChannel.scala index f7c9e1cc..1b034a9b 100644 --- a/shared/src/main/scala/gopher/ReadChannel.scala +++ b/shared/src/main/scala/gopher/ReadChannel.scala @@ -32,12 +32,12 @@ trait ReadChannel[F[_], A]: * async version of read. Immediatly return future, which will contains result of read or failur with StreamClosedException * in case of stream is closed. */ - def aread:F[A] = + def aread():F[A] = asyncMonad.adoptCallbackStyle(f => addReader(SimpleReader(f))) - transparent inline def read: A = await(aread)(using rAsyncMonad) + transparent inline def read(): A = await(aread())(using rAsyncMonad) - transparent inline def ? : A = await(aread)(using rAsyncMonad) + transparent inline def ? : A = await(aread())(using rAsyncMonad) /** * return F which contains sequence from first `n` elements. @@ -49,7 +49,7 @@ trait ReadChannel[F[_], A]: try { var c = 0 while(c < n) { - val a = read + val a = read() b.addOne(a) c = c + 1 } @@ -59,7 +59,7 @@ trait ReadChannel[F[_], A]: b.result() } - def aOptRead: F[Option[A]] = + def aOptRead(): F[Option[A]] = asyncMonad.adoptCallbackStyle( f => addReader(SimpleReader{ x => x match case Failure(ex: ChannelClosedException) => f(Success(None)) @@ -68,14 +68,14 @@ trait ReadChannel[F[_], A]: }) ) - transparent inline def optRead: Option[A] = await(aOptRead)(using rAsyncMonad) + transparent inline def optRead(): Option[A] = await(aOptRead())(using rAsyncMonad) def foreach_async(f: A=>F[Unit]): F[Unit] = given CpsAsyncMonad[F] = asyncMonad async[F]{ var done = false while(!done) { - optRead match + optRead() match case Some(v) => await(f(v)) case None => done = true } @@ -120,7 +120,7 @@ trait ReadChannel[F[_], A]: async[F] { var s = s0 while{ - optRead match + optRead() match case Some(a) => s = await(f(s,a)) true @@ -139,9 +139,9 @@ trait ReadChannel[F[_], A]: asyncMonad.spawn(async[F]{ var done = false while(!done) { - this.optRead match + this.optRead() match case Some(a) => - x.optRead match + x.optRead() match case Some(b) => retval.write((a,b)) case None => diff --git a/shared/src/main/scala/gopher/impl/ChFlatMappedReadChannel.scala b/shared/src/main/scala/gopher/impl/ChFlatMappedReadChannel.scala index ce42534c..d14355b3 100644 --- a/shared/src/main/scala/gopher/impl/ChFlatMappedReadChannel.scala +++ b/shared/src/main/scala/gopher/impl/ChFlatMappedReadChannel.scala @@ -22,11 +22,11 @@ class ChFlatMappedReadChannel[F[_], A, B](prev: ReadChannel[F,A], f: A=>ReadChan given CpsSchedulingMonad[F] = gopherApi.asyncMonad async[F]{ while{ - prev.optRead match + prev.optRead() match case Some(a) => val internal = f(a) while{ - internal.optRead match + internal.optRead() match case Some(b) => bChannel.write(b) true diff --git a/shared/src/test/scala/gopher/channels/AsyncChannelTests.scala b/shared/src/test/scala/gopher/channels/AsyncChannelTests.scala index 636ab308..9f75f425 100644 --- a/shared/src/test/scala/gopher/channels/AsyncChannelTests.scala +++ b/shared/src/test/scala/gopher/channels/AsyncChannelTests.scala @@ -21,7 +21,7 @@ class AsyncChannelTests extends FunSuite { val consumer = async{ var sum = 0 - while{val a = channel.read + while{val a = channel.read() sum += a a < MAX_N } do () diff --git a/shared/src/test/scala/gopher/channels/ChannelCloseSuite.scala b/shared/src/test/scala/gopher/channels/ChannelCloseSuite.scala index 0e8baf28..eb7395c9 100644 --- a/shared/src/test/scala/gopher/channels/ChannelCloseSuite.scala +++ b/shared/src/test/scala/gopher/channels/ChannelCloseSuite.scala @@ -61,10 +61,10 @@ class ChannelCloseSuite extends FunSuite val p = async { channel <~ 1 channel.close() - q1 = channel.read + q1 = channel.read() } val afterClose = p flatMap { _ => async{ - val a = channel.read + val a = channel.read() q2 = 2 } } @@ -87,10 +87,10 @@ class ChannelCloseSuite extends FunSuite val p = async { channel <~ 1 channel.close() - q1 = channel.read // will be unblocked after close and tbrwo exception + q1 = channel.read() // will be unblocked after close and tbrwo exception } val consumer = async{ - q3 = channel.read // will be run + q3 = channel.read() // will be run q2 = 2 } @@ -116,7 +116,7 @@ class ChannelCloseSuite extends FunSuite channel.close() @volatile var q = 0 val fp = async { - val done = channel.done.read + val done = channel.done.read() q = 1 } fp map (_ => assert(q == 1)) diff --git a/shared/src/test/scala/gopher/channels/ChannelFilterSuite.scala b/shared/src/test/scala/gopher/channels/ChannelFilterSuite.scala index a6e9e2a9..53eb3480 100644 --- a/shared/src/test/scala/gopher/channels/ChannelFilterSuite.scala +++ b/shared/src/test/scala/gopher/channels/ChannelFilterSuite.scala @@ -28,7 +28,7 @@ class ChannelFilterSuite extends FunSuite: async { var i = 0 while(i < 50) { - val x = filtered.read + val x = filtered.read() assert( x % 2 == 0) i=i+1 } diff --git a/shared/src/test/scala/gopher/channels/DuppedChannelsSuite.scala b/shared/src/test/scala/gopher/channels/DuppedChannelsSuite.scala index 2aee1276..93c392a0 100644 --- a/shared/src/test/scala/gopher/channels/DuppedChannelsSuite.scala +++ b/shared/src/test/scala/gopher/channels/DuppedChannelsSuite.scala @@ -20,8 +20,8 @@ class DuppedChannelsSuite extends FunSuite { val ch = makeChannel[String]() val dupped = ch.dup() val r0 = ch.awrite("1") - val r1 = dupped._1.aread - val r2 = dupped._2.aread + val r1 = dupped._1.aread() + val r2 = dupped._2.aread() val r = for(v1 <- r1; v2 <- r2) yield (v1,v2) r map {x => @@ -55,8 +55,8 @@ class DuppedChannelsSuite extends FunSuite { ch.close() } for{ fx <- f1 - x <- in1.aread - r <- in1.aread.transformWith { + x <- in1.aread() + r <- in1.aread().transformWith { case Success(u) => Future failed new IllegalStateException("Mist be closed") case Failure(u) => diff --git a/shared/src/test/scala/gopher/channels/ExpireChannelSuite.scala b/shared/src/test/scala/gopher/channels/ExpireChannelSuite.scala index e8afbcf1..edd71c9e 100644 --- a/shared/src/test/scala/gopher/channels/ExpireChannelSuite.scala +++ b/shared/src/test/scala/gopher/channels/ExpireChannelSuite.scala @@ -23,7 +23,7 @@ class ExpireChannelSuite extends FunSuite { val ch = makeChannel[Int](10).withExpiration(300 milliseconds, false) val emptyRead = for {_ <- ch.awrite(1) _ <- Time.asleep(400 milliseconds) - r <- ch.aread + r <- ch.aread() } yield r async { @@ -46,7 +46,7 @@ class ExpireChannelSuite extends FunSuite { for { _ <- ch.awrite(1) _ <- Time.asleep(10 milliseconds) - r <- ch.aread + r <- ch.aread() } yield assert(r==1) } @@ -68,12 +68,12 @@ class ExpireChannelSuite extends FunSuite { test("expire must be an order") { val ch = makeChannel[Int](10).withExpiration(300 milliseconds, false) - val fr1 = ch.aread - val fr2 = ch.aread + val fr1 = ch.aread() + val fr2 = ch.aread() for { _ <- ch.awriteAll(List(1,2)) _ <- Time.asleep(10 milliseconds) - fr3 = ch.aread + fr3 = ch.aread() r3 <- fr3.withTimeout(1 second).transform{ case Failure(ex: TimeoutException) => Success(()) diff --git a/shared/src/test/scala/gopher/channels/FibonnachySimpleTests.scala b/shared/src/test/scala/gopher/channels/FibonnachySimpleTests.scala index 208bcac5..e7e5083d 100644 --- a/shared/src/test/scala/gopher/channels/FibonnachySimpleTests.scala +++ b/shared/src/test/scala/gopher/channels/FibonnachySimpleTests.scala @@ -54,7 +54,7 @@ class FibbonachySimpleTest extends FunSuite { val start = starter(fib,q) async{ for( i <- 1 to n) { - val x = fib.read + val x = fib.read() acceptor(x) } q <~ 1 diff --git a/shared/src/test/scala/gopher/channels/FoldSelectSuite.scala b/shared/src/test/scala/gopher/channels/FoldSelectSuite.scala index 822a6f13..31f22de7 100644 --- a/shared/src/test/scala/gopher/channels/FoldSelectSuite.scala +++ b/shared/src/test/scala/gopher/channels/FoldSelectSuite.scala @@ -45,7 +45,7 @@ class FoldSelectSuite extends FunSuite val read = async { for(i <- 1 to 100) yield { - val x = out.read + val x = out.read() x } } diff --git a/shared/src/test/scala/gopher/channels/InputOpsSuite.scala b/shared/src/test/scala/gopher/channels/InputOpsSuite.scala index 4998ddd3..72070919 100644 --- a/shared/src/test/scala/gopher/channels/InputOpsSuite.scala +++ b/shared/src/test/scala/gopher/channels/InputOpsSuite.scala @@ -33,7 +33,7 @@ class InputOpsSuite extends FunSuite { val ch = makeChannel[String]() ch.awriteAll(List("qqq", "AAA","123","1234","12345")) val filteredCh = ch filter (_.contains("A")) - filteredCh.aread map { x => assert(x == "AAA") } + filteredCh.aread() map { x => assert(x == "AAA") } } @@ -44,15 +44,15 @@ class InputOpsSuite extends FunSuite { val ch2 = makeChannel[Int]() ch2.awriteAll(List(1, 2, 3, 4, 5, 6)) val zipped = ch1 zip ch2 - for{ r1 <- zipped.aread + for{ r1 <- zipped.aread() _ = assert( r1 == ("qqq",1) ) - r2 <- zipped.aread + r2 <- zipped.aread() _ = assert( r2 == ("AAA",2) ) - r3 <- zipped.aread + r3 <- zipped.aread() _ = assert( r3 == ("123",3) ) - r4 <- zipped.aread + r4 <- zipped.aread() _ = assert( r4 == ("1234",4) ) - r5 <- zipped.aread + r5 <- zipped.aread() l = assert( r5 == ("12345",5) ) } yield l } @@ -63,13 +63,13 @@ class InputOpsSuite extends FunSuite { val ch2 = List(1,2,3,4,5,6).asReadChannel val zipped = ch1 zip ch2 for{ - r1 <- zipped.aread + r1 <- zipped.aread() a1 = assert(r1 == (1, 1)) - r2 <- zipped.aread + r2 <- zipped.aread() a2 = assert( (r2 == (2,2)) ) r3 <- async{ try - zipped.read + zipped.read() assert(""=="exception should be called before") catch case ex: Throwable => @@ -100,11 +100,11 @@ class InputOpsSuite extends FunSuite { val ch = makeChannel[Int]() val zipped = ch zip ch ch.awriteAll(List(1,2,3,4,5,6,7,8)) - for{ r1 <- zipped.aread + for{ r1 <- zipped.aread() a1 = assert( Set((1,2),(2,1)) contains r1 ) - r2 <- zipped.aread + r2 <- zipped.aread() a2 = assert( Set((3,4),(4,3)) contains r2 ) - r3 <- zipped.aread + r3 <- zipped.aread() a3 = assert( Set((5,6),(6,5)) contains r3 ) } yield a3 } @@ -115,11 +115,11 @@ class InputOpsSuite extends FunSuite { val ch1 = makeChannel[Int]() val ch2 = makeChannel[Int]() - val ar1 = (ch1 | ch2).aread + val ar1 = (ch1 | ch2).aread() ch1.awrite(1) for{ r1 <- ar1 - ar2 = (ch1 | ch2).aread + ar2 = (ch1 | ch2).aread() _ = ch2.awrite(2) r2 <- ar2 } yield { @@ -135,8 +135,8 @@ class InputOpsSuite extends FunSuite { val ch1 = makeChannel[Int]() val ch2 = makeChannel[Int]() - val ar1 = (ch1 | ch2).aread - val ar2 = (ch1 | ch2).aread + val ar1 = (ch1 | ch2).aread() + val ar2 = (ch1 | ch2).aread() ch1.awrite(1) ch2.awrite(2) @@ -153,7 +153,7 @@ class InputOpsSuite extends FunSuite { //} r3 <- async { try { - await((ch1 | ch2).aread.withTimeout(300 milliseconds)) + await((ch1 | ch2).aread().withTimeout(300 milliseconds)) } catch { case ex: TimeoutException => assert(true) @@ -167,10 +167,10 @@ class InputOpsSuite extends FunSuite { test("reflexive or Q|Q") { val ch = makeChannel[Int]() val aw1 = ch.awrite(1) - val ar1 = (ch | ch).aread + val ar1 = (ch | ch).aread() for {r1 <- ar1 _ = assert(r1 == 1) - ar2 = (ch | ch).aread + ar2 = (ch | ch).aread() //r2_1 <- recoverToSucceededIf[TimeoutException] { // timeouted(ar2, 300 milliseconds) //} @@ -195,8 +195,8 @@ class InputOpsSuite extends FunSuite { val aw1 = ch1.awrite(1) val aw2 = ch2.awrite(2) val chOr = (ch1 | ch2) - val ar1 = chOr.aread - val ar2 = chOr.aread + val ar1 = chOr.aread() + val ar2 = chOr.aread() for {r1 <- ar1 r2 <- ar2 } yield assert( ((r1,r2)==(1,2)) ||((r1,r2)==(2,1)) ) @@ -319,10 +319,10 @@ class InputOpsSuite extends FunSuite { ch.awriteAll(List(10,12,34,43)) for{ - r1 <- ch.aread - r2 <- ch.aread - r3 <- ch.aread - r4 <- ch.aread + r1 <- ch.aread() + r2 <- ch.aread() + r3 <- ch.aread() + r4 <- ch.aread() } yield assert((r1,r2,r3,r4) == (10,12,34,43) ) } @@ -345,7 +345,7 @@ class InputOpsSuite extends FunSuite { val ch2 = makeChannel[Int](10) val fs = async { val sum = ch1.fold(0){ (s,n) => - val n1 = ch2.read + val n1 = ch2.read() //s+(n1+n2) -- stack overflow in 2.11.8 compiler. TODO: submit bug s+(n+n1) } diff --git a/shared/src/test/scala/gopher/channels/MacroSelectSuite.scala b/shared/src/test/scala/gopher/channels/MacroSelectSuite.scala index 12fb48dd..a25a1c77 100644 --- a/shared/src/test/scala/gopher/channels/MacroSelectSuite.scala +++ b/shared/src/test/scala/gopher/channels/MacroSelectSuite.scala @@ -383,8 +383,8 @@ class MacroSelectSuite extends FunSuite case x:ch2.read => x*5 } } - val fs1 = chs.aread - val fs2 = chs.aread + val fs1 = chs.aread() + val fs2 = chs.aread() val s1 = await(fs1) val s2 = await(fs2) assert(s1==3 || s1==10) @@ -398,7 +398,7 @@ class MacroSelectSuite extends FunSuite val f1 = ch.awrite(1) val f2 = ch.awrite(2) async { - val x = await(ch.aread) + val x = await(ch.aread()) val x2 = Try(await(f2.failed)) assert(x == 1) assert(x2.get.isInstanceOf[ChannelClosedException]) diff --git a/shared/src/test/scala/gopher/channels/SelectSuite.scala b/shared/src/test/scala/gopher/channels/SelectSuite.scala index fc225a04..7d11c2bb 100644 --- a/shared/src/test/scala/gopher/channels/SelectSuite.scala +++ b/shared/src/test/scala/gopher/channels/SelectSuite.scala @@ -55,7 +55,7 @@ class SelectSuite extends FunSuite // but when reading instead onRead // TODO: submit bug to doty val consumer = select.loop.onRead(channel1) { i1 => - val i2 = channel2.read + val i2 = channel2.read() sum = sum+i1 + i2 (i1 < 1000) } .runAsync() diff --git a/shared/src/test/scala/gopher/channels/SelectTimeoutSuite.scala b/shared/src/test/scala/gopher/channels/SelectTimeoutSuite.scala index f7624130..0d016fa0 100644 --- a/shared/src/test/scala/gopher/channels/SelectTimeoutSuite.scala +++ b/shared/src/test/scala/gopher/channels/SelectTimeoutSuite.scala @@ -32,7 +32,7 @@ class SelectTimeoutSuite extends FunSuite } } val f1 = ch1.awrite(1) - val x = r.read + val x = r.read() assert(x==1) } } @@ -51,7 +51,7 @@ class SelectTimeoutSuite extends FunSuite -1 } } - val x = r.read + val x = r.read() assert(x == -1) } } diff --git a/shared/src/test/scala/gopher/channels/UnbufferedSelectSuite.scala b/shared/src/test/scala/gopher/channels/UnbufferedSelectSuite.scala index a25ed7b9..5da0c3d7 100644 --- a/shared/src/test/scala/gopher/channels/UnbufferedSelectSuite.scala +++ b/shared/src/test/scala/gopher/channels/UnbufferedSelectSuite.scala @@ -24,7 +24,7 @@ class UnbufferedSelectSuite extends FunSuite assert(!w1.isCompleted) - val r1 = channel1.aread + val r1 = channel1.aread() async { await(w1) diff --git a/shared/src/test/scala/hofasync/TestSharedMin.scala b/shared/src/test/scala/hofasync/TestSharedMin.scala index b8440c29..c03461b9 100644 --- a/shared/src/test/scala/hofasync/TestSharedMin.scala +++ b/shared/src/test/scala/hofasync/TestSharedMin.scala @@ -14,7 +14,7 @@ class TestSharedMin extends munit.FunSuite { val gopherApi = SharedGopherAPI.apply[Future]() val ch = gopherApi.makeChannel[Int](1) val fw1 = ch.awrite(2) - val fr1 = ch.aread + val fr1 = ch.aread() //implicit val printCode = cps.macroFlags.PrintCode async[Future] { val r1 = await(fr1) From 1edd27f55ede737c627743fd85bd5679f4272a8d Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Fri, 26 Mar 2021 09:37:41 +0200 Subject: [PATCH 056/161] ported examples: FinbonaccyFoldSuite --- README.md | 30 ++++++------------- .../scala/examples}/FibonaccyFoldSuite.scala | 26 +++++++++------- 2 files changed, 25 insertions(+), 31 deletions(-) rename {0.99.x/src/test/scala/example => shared/src/test/scala/examples}/FibonaccyFoldSuite.scala (57%) diff --git a/README.md b/README.md index 32143e9d..1fe80b10 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,6 @@ Scala-gopher is open source (license is Apache2); binaries are available from th Note, which this is not an emulation of go language structures in Scala, but rather a reimplementation of the main ideas in 'scala-like' manner. - ### Initialization You need a given of gopherApi for creating channels and selectors. @@ -40,7 +39,7 @@ Note, which this is not an emulation of go language structures in Scala, but rat ## Channels Channels are used for asynchronous communication between execution flows. -When using channel inside *async* block, you can look at one as on classic blocked queue with fixed size with methods read and write: +When using channel inside async block, you can look at one as on classic blocked queue with fixed size with methods read and write: val channel = makeChannel[Int]; @@ -50,7 +49,7 @@ When using channel inside *async* block, you can look at one as on classic block } ...... async { - val i = channel.read + val i = channel.read() } @@ -62,7 +61,7 @@ Blocking operations can be used only inside `await` blocks. Outside we can use asynchronous version: * `channel.awrite(x)` will write `x` and return to us `Future[Unit]` which will be executed after x will send -* `channel.aread` will return future to the value, which will be read. +* `channel.aread()` will return future to the value, which will be read. Channels can be closed. After this attempt to write will cause throwing 'ClosedChannelException.' Reading will be still possible up to 'last written value', after this attempt to read will cause the same exception. Also, each channel provides `done` input for firing close events. @@ -85,13 +84,12 @@ val producer = channel.awrite(1 to 1000) val consumer = async { var done = false while(!done) - val i = channel.read + val i = channel.read() sum = sum + i if i==1000 then done = true } -Await.ready(consumer, 5.second) ~~~ last loop can be repharased in more scala wat as: @@ -100,20 +98,12 @@ last loop can be repharased in more scala wat as: val sum = (channel.take(1000)).fold(0)((s,i) => s+i) ~~~ -Scala Iterable can be represented as `channels.Input` via method `gopherApi.iterableInput`. Also, we can use Scala futures as channels, which produce one value and then closes. For obtaining such input use `gopherApi.futureInput`. +Scala Iterable can be represented as `ReadChannel` via extension method `asReadChannel`. + +Also, we can use Scala futures as channels, which produce one value and then closes. For obtaining such input use `gopherApi.futureInput`. `|` (i.e. or) operator used for merged inputs, i.e. `(x|y).read` will read a value from channel x or y when one will be available. -For each input and output you can create a facility with tracked timeout, i.e. if `in` is input, then -~~~ scala - val (inReady, inTimeouts) = in.withInputTimeouts(10 seconds) -~~~ -will return two inputs, where reading from `inReady` will return the same as reading from `in`. And if waiting for reading takes longer than 10 seconds then the value of timeout will be available in `inTimeouts`. Analogically we can create output with timeouts: -~~~ scala - val (outReady, outTimeouts) = out.withOutputTimeouts(10 seconds) -~~~ - - Also, note that you can provide own Input and Output implementations by implementing callback `cbread` and `cbwrite` methods. @@ -141,10 +131,8 @@ async[Future]{ select accepts partial functions syntax, left parts in `case` clauses must have the following form * `v:channel.read` (for reading from channel) - * `v:Type if (v==read(ch))` (for reading from channel or future) * `v:channel.write if (v==expr)` (for writing `expr` into channel). - * `v:Type if (v==write(ch,expr))` (for writing `expr` into channel). - * `_` - for 'idle' action in unblocking select. (TODO: .. ) + * `v:Time.after if (v==expr)` (for timeouts). Inside case actions, we can use blocking read/writes and await operations. @@ -165,7 +153,7 @@ TODO More than one variables in state can be modeled with partial function case syntax: ~~~ scala -val fib = select.afold((0,1)) { case ((x,y), s) => +val fib = select.fold((0,1)) { case ((x,y), s) => s match { case x:channel.write => (y,y+x) case q:quit.read => select.exit((x,y)) diff --git a/0.99.x/src/test/scala/example/FibonaccyFoldSuite.scala b/shared/src/test/scala/examples/FibonaccyFoldSuite.scala similarity index 57% rename from 0.99.x/src/test/scala/example/FibonaccyFoldSuite.scala rename to shared/src/test/scala/examples/FibonaccyFoldSuite.scala index 0be8c498..fe48b58a 100644 --- a/0.99.x/src/test/scala/example/FibonaccyFoldSuite.scala +++ b/shared/src/test/scala/examples/FibonaccyFoldSuite.scala @@ -1,11 +1,16 @@ package example -import gopher.channels._ -import org.scalatest._ +import gopher._ +import cps._ +import munit._ import scala.concurrent._ import scala.language._ +import cps.monads.FutureAsyncMonad +import scala.concurrent.ExecutionContext.Implicits.global + + /* * code from go tutorial: http://tour.golang.org/#66 but with fold instead foreach * @@ -13,18 +18,18 @@ import scala.language._ object FibonaccyFold { - import CommonTestObjects.gopherApi._ + given Gopher[Future] = SharedGopherAPI.apply[Future]() - import scala.concurrent.ExecutionContext.Implicits.global - def fibonacci(c: Output[Long], quit: Input[Int]): Future[(Long,Long)] = - select.afold((0L,1L)) { case ((x,y),s) => - s match { - case x: c.write => (y, x+y) + def fibonacci(c: WriteChannel[Future,Long], quit: ReadChannel[Future,Int]): Future[(Long,Long)] = async { + select.fold((0L,1L)) { case ((x,y),s) => + s.select{ + case wx: c.write if wx == x => (y, x+y) case q: quit.read => - select.exit((x,y)) + SelectFold.Done((x,y)) } } + } def run(n:Int, acceptor: Long => Unit ): Future[(Long,Long)] = { @@ -39,8 +44,9 @@ object FibonaccyFold { } -class FibonaccyFoldSuite extends AsyncFunSuite +class FibonaccyFoldSuite extends FunSuite { + test("fibonaccy must be processed up to 50") { FibonaccyFold.run(50, _ => () ).map(last => assert(last._2 != 0)) From 6aadee5626f02fc20b9d499df0af777115b58a0a Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Fri, 26 Mar 2021 10:17:00 +0200 Subject: [PATCH 057/161] added isClosed to Channel public API --- js/src/main/scala/gopher/impl/BaseChannel.scala | 3 +++ .../main/scala/gopher/impl/GuardedSPSCBaseChannel.scala | 3 +++ jvm/src/main/scala/gopher/impl/PromiseChannel.scala | 3 ++- shared/src/main/scala/gopher/Channel.scala | 2 ++ shared/src/main/scala/gopher/ChannelWithExpiration.scala | 2 ++ .../src/main/scala/gopher/impl/ChFlatMappedChannel.scala | 4 ++++ shared/src/main/scala/gopher/impl/FilteredChannel.scala | 8 ++++++++ shared/src/main/scala/gopher/impl/MappedChannel.scala | 6 ++++++ 8 files changed, 30 insertions(+), 1 deletion(-) diff --git a/js/src/main/scala/gopher/impl/BaseChannel.scala b/js/src/main/scala/gopher/impl/BaseChannel.scala index 3df207aa..3bf2ef51 100644 --- a/js/src/main/scala/gopher/impl/BaseChannel.scala +++ b/js/src/main/scala/gopher/impl/BaseChannel.scala @@ -20,6 +20,9 @@ abstract class BaseChannel[F[_],A](override val gopherApi: JSGopher[F]) extends closed = true processClose() + override def isClosed: Boolean = + closed + protected def submitTask(f: ()=>Unit ): Unit = JSExecutionContext.queue.execute{ () => try diff --git a/jvm/src/main/scala/gopher/impl/GuardedSPSCBaseChannel.scala b/jvm/src/main/scala/gopher/impl/GuardedSPSCBaseChannel.scala index 52ea1def..9f05114d 100644 --- a/jvm/src/main/scala/gopher/impl/GuardedSPSCBaseChannel.scala +++ b/jvm/src/main/scala/gopher/impl/GuardedSPSCBaseChannel.scala @@ -58,6 +58,9 @@ abstract class GuardedSPSCBaseChannel[F[_]:CpsAsyncMonad,A](override val gopherA publishedClosed.set(true) controlExecutor.submit(stepRunnable) + def isClosed: Boolean = + publishedClosed.get() + protected def step(): Unit diff --git a/jvm/src/main/scala/gopher/impl/PromiseChannel.scala b/jvm/src/main/scala/gopher/impl/PromiseChannel.scala index 831f5627..22be60c2 100644 --- a/jvm/src/main/scala/gopher/impl/PromiseChannel.scala +++ b/jvm/src/main/scala/gopher/impl/PromiseChannel.scala @@ -70,7 +70,8 @@ import scala.util.Failure if (ref.get() eq null) closeAll() - + def isClosed: Boolean = + closed.get() def step(): Unit = val ar = ref.get() diff --git a/shared/src/main/scala/gopher/Channel.scala b/shared/src/main/scala/gopher/Channel.scala index 54e87bd3..a281add7 100644 --- a/shared/src/main/scala/gopher/Channel.scala +++ b/shared/src/main/scala/gopher/Channel.scala @@ -31,6 +31,8 @@ trait Channel[F[_],W,R] extends WriteChannel[F,W] with ReadChannel[F,R] with Clo override def filterAsync(p: R=>F[Boolean]): Channel[F,W,R] = FilteredAsyncChannel(this,p) + def isClosed: Boolean + end Channel diff --git a/shared/src/main/scala/gopher/ChannelWithExpiration.scala b/shared/src/main/scala/gopher/ChannelWithExpiration.scala index 811752ca..5bfa5fdf 100644 --- a/shared/src/main/scala/gopher/ChannelWithExpiration.scala +++ b/shared/src/main/scala/gopher/ChannelWithExpiration.scala @@ -26,6 +26,8 @@ class ChannelWithExpiration[F[_],W,R](internal: Channel[F,W,R], ttl: FiniteDurat override def close(): Unit = internal.close() + override def isClosed: Boolean = internal.isClosed + def qqq: Int = 0 diff --git a/shared/src/main/scala/gopher/impl/ChFlatMappedChannel.scala b/shared/src/main/scala/gopher/impl/ChFlatMappedChannel.scala index 7f37ed21..9d2170f5 100644 --- a/shared/src/main/scala/gopher/impl/ChFlatMappedChannel.scala +++ b/shared/src/main/scala/gopher/impl/ChFlatMappedChannel.scala @@ -11,3 +11,7 @@ class ChFlatMappedChannel[F[_],W,RA,RB](internal: Channel[F,W,RA], f: RA=>ReadCh override def close(): Unit = internal.close() + override def isClosed: Boolean = + internal.isClosed + + diff --git a/shared/src/main/scala/gopher/impl/FilteredChannel.scala b/shared/src/main/scala/gopher/impl/FilteredChannel.scala index ad460434..dca8b27b 100644 --- a/shared/src/main/scala/gopher/impl/FilteredChannel.scala +++ b/shared/src/main/scala/gopher/impl/FilteredChannel.scala @@ -11,6 +11,9 @@ class FilteredChannel[F[_],W,R](internal: Channel[F,W,R], p: R => Boolean) exten override def close(): Unit = internal.close() + override def isClosed: Boolean = + internal.isClosed + class FilteredAsyncChannel[F[_],W,R](internal: Channel[F,W,R], p: R => F[Boolean]) extends FilteredAsyncReadChannel[F,R](internal, p) with Channel[F,W,R]: @@ -20,3 +23,8 @@ class FilteredAsyncChannel[F[_],W,R](internal: Channel[F,W,R], p: R => F[Boolea override def close(): Unit = internal.close() + + override def isClosed: Boolean = + internal.isClosed + + \ No newline at end of file diff --git a/shared/src/main/scala/gopher/impl/MappedChannel.scala b/shared/src/main/scala/gopher/impl/MappedChannel.scala index ce58dbb6..ad1fac12 100644 --- a/shared/src/main/scala/gopher/impl/MappedChannel.scala +++ b/shared/src/main/scala/gopher/impl/MappedChannel.scala @@ -11,6 +11,9 @@ class MappedChannel[F[_],W,RA,RB](internal: Channel[F,W,RA], f: RA=>RB) extends override def close(): Unit = internal.close() + override def isClosed: Boolean = + internal.isClosed + class MappedAsyncChannel[F[_],W,RA,RB](internal: Channel[F,W,RA], f: RA=>F[RB]) extends MappedAsyncReadChannel[F,RA,RB](internal, f) with Channel[F,W,RB]: @@ -20,3 +23,6 @@ class MappedAsyncChannel[F[_],W,RA,RB](internal: Channel[F,W,RA], f: RA=>F[RB]) override def close(): Unit = internal.close() + + override def isClosed: Boolean = + internal.isClosed From d074aa7f15d9904b7cf0491cc3e14d370efdacdb Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Fri, 26 Mar 2021 10:45:34 +0200 Subject: [PATCH 058/161] restored Sieve example --- shared/src/test/scala/examples/Sieve.scala | 46 ++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 shared/src/test/scala/examples/Sieve.scala diff --git a/shared/src/test/scala/examples/Sieve.scala b/shared/src/test/scala/examples/Sieve.scala new file mode 100644 index 00000000..01c211b7 --- /dev/null +++ b/shared/src/test/scala/examples/Sieve.scala @@ -0,0 +1,46 @@ +package examples + +import scala.concurrent.{Channel=>_,_} +import cps._ +import cps.monads.FutureAsyncMonad +import gopher._ + +import scala.concurrent.ExecutionContext.Implicits.global + + + +object Sieve { + + given Gopher[Future] = SharedGopherAPI.apply[Future]() + + + def run(in: Channel[Future,Int, Int], out: Channel[Future,Int,Int]): Future[Unit] = async[Future] { + var middle: ReadChannel[Future,Int] = in + while (!in.isClosed) { + val x = middle.read() + out.write(x) + middle = middle.filter(_ % x != 0) + } + } + + + + def runRec(in: Channel[Future,Int, Int], out: Channel[Future,Int, Int]): Future[Unit] = async[Future] { + val x = in.read() + out.write(x) + await(runRec(in.filter(_ % x != 0), out)) + } + + + def runFold(in:Channel[Future,Int, Int], out: Channel[Future,Int, Int]) = async { // ??? + + + select.fold(in){ (s, g) => + val x = s.read() + out.write(x) + s.filter(_ % x != 0) + } + } + + +} From 1477362bcf35639ef18ad2e8ea68d66685618e49 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Fri, 26 Mar 2021 11:15:51 +0200 Subject: [PATCH 059/161] ported SieveSuite --- shared/src/test/scala/examples/Sieve.scala | 2 +- .../src/test/scala/examples}/SieveSuite.scala | 62 +++++++++---------- 2 files changed, 32 insertions(+), 32 deletions(-) rename {0.99.x/src/test/scala/example => shared/src/test/scala/examples}/SieveSuite.scala (58%) diff --git a/shared/src/test/scala/examples/Sieve.scala b/shared/src/test/scala/examples/Sieve.scala index 01c211b7..a57ddf66 100644 --- a/shared/src/test/scala/examples/Sieve.scala +++ b/shared/src/test/scala/examples/Sieve.scala @@ -9,7 +9,7 @@ import scala.concurrent.ExecutionContext.Implicits.global -object Sieve { +object Sieve1 { given Gopher[Future] = SharedGopherAPI.apply[Future]() diff --git a/0.99.x/src/test/scala/example/SieveSuite.scala b/shared/src/test/scala/examples/SieveSuite.scala similarity index 58% rename from 0.99.x/src/test/scala/example/SieveSuite.scala rename to shared/src/test/scala/examples/SieveSuite.scala index 2f28f73c..74b5c3be 100644 --- a/0.99.x/src/test/scala/example/SieveSuite.scala +++ b/shared/src/test/scala/examples/SieveSuite.scala @@ -1,22 +1,27 @@ package example import gopher._ -import gopher.channels.CommonTestObjects.gopherApi._ -import gopher.channels._ -import org.scalatest._ +import cps._ +import munit._ import scala.concurrent.{Channel => _, _} import scala.language.postfixOps +import cps.monads.FutureAsyncMonad +import scala.concurrent.ExecutionContext.Implicits.global + + /** * this is direct translation from appropriative go example. **/ object Sieve { - import scala.concurrent.ExecutionContext.Implicits.global - def generate(n:Int, quit:Promise[Boolean]):Channel[Int] = + given gopherApi: Gopher[Future] = SharedGopherAPI.apply[Future]() + + + def generate(n:Int, quit:Promise[Boolean]):Channel[Future,Int,Int] = { val channel = makeChannel[Int]() channel.awriteAll(2 to n) foreach (_ => quit success true) @@ -25,14 +30,14 @@ object Sieve // direct translation from go - def filter0(in:Channel[Int]):Input[Int] = + def filter0(in:Channel[Future,Int,Int]):ReadChannel[Future,Int] = { val filtered = makeChannel[Int]() - var proxy: Input[Int] = in; - go { + var proxy: ReadChannel[Future, Int] = in; + async { // since proxy is var, we can't select from one in forever loop. while(true) { - val prime = proxy.read + val prime = proxy.read() proxy = proxy.filter(_ % prime != 0) filtered.write(prime) } @@ -40,26 +45,13 @@ object Sieve filtered } - // use effected input - /* - def filter(in:Channel[Int]):Input[Int] = - { - val filtered = makeChannel[Int]() - val sieve = makeEffectedInput(in) - sieve.aforeach { prime => - sieve apply (_.filter(_ % prime != 0)) - filtered <~ prime - } - filtered - } - */ - def filter1(in:Channel[Int]):Input[Int] = + def filter1(in:Channel[Future,Int,Int]):ReadChannel[Future,Int] = { val q = makeChannel[Int]() val filtered = makeChannel[Int]() select.afold(in){ (ch, s) => - s match { + s.select{ case prime: ch.read => filtered.write(prime) ch.filter(_ % prime != 0) @@ -68,35 +60,43 @@ object Sieve filtered } - def primes(n:Int, quit: Promise[Boolean]):Input[Int] = + def primes(n:Int, quit: Promise[Boolean]):ReadChannel[Future,Int] = filter1(generate(n,quit)) } -class SieveSuite extends AsyncFunSuite +class SieveSuite extends FunSuite { + import Sieve.gopherApi + + test("last prime before 1000") { val quit = Promise[Boolean]() - val quitInput = futureInput(quit.future) + val quitInput = quit.future.asChannel val pin = Sieve.primes(1000,quit) var lastPrime=0; - val future = select.forever { + async { + select.loop{ case p: pin.read => if (false) { System.err.print(p) System.err.print(" ") } lastPrime=p + true case q: quitInput.read => //System.err.println() - CurrentFlowTermination.exit(()) - } - future map (_ => assert( lastPrime == 997)) + false + } + assert( lastPrime == 997 ) + } } + + } From f354fb76f3d1457d88c4325222c5f298d1fcdfc6 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Sat, 27 Mar 2021 16:40:58 +0200 Subject: [PATCH 060/161] README in progress --- README.md | 126 +++++++++++------- js/src/main/scala/gopher/JSGopher.scala | 3 + jvm/src/main/scala/gopher/JVMGopher.scala | 4 +- shared/src/main/scala/gopher/GopherAPI.scala | 1 - .../scala/gopher/channels/SelectSuite.scala | 2 - 5 files changed, 81 insertions(+), 55 deletions(-) diff --git a/README.md b/README.md index 1fe80b10..c4288041 100644 --- a/README.md +++ b/README.md @@ -4,14 +4,15 @@ ### Dependences: -For scala 3.0.0-RC1: +For scala 3.0.0-RC2: - libraryDependencies += "com.github.rssh" %% "scala-gopher" % "2.0.0-RC1" + libraryDependencies += "com.github.rssh" %% "scala-gopher" % "2.0.0-RC2" For scala2: libraryDependencies += "com.github.rssh" %% "scala-gopher" % "0.99.15" + (Documentation for scala2 look at README at 0.99x branch) Scala-gopher is open source (license is Apache2); binaries are available from the maven-central repository. @@ -28,12 +29,15 @@ Note, which this is not an emulation of go language structures in Scala, but rat You need a given of gopherApi for creating channels and selectors. import gopher._ + import cps._ + import cps.monads.FutureAsyncMonad ...... given Gopher[Future]() - type parameter is a monad, which should implement CpsSchedulingMonad typeclass. + type parameter can be a monad, which should implement CpsSchedulingMonad typeclass. + ## Channels @@ -70,10 +74,10 @@ Closing channels are not mandatory; unreachable channels are garbage-collected r Channels can be buffered and unbuffered. In a unbuffered channel, write return control to the caller after another side actually will start processing; buffered channel force provider to wait only if internal channel buffer is full. -Also, you can use only `Input` or `Output` interfaces, where an appropriative read/write operations are defined. -For `Input`, exists usual collection functions, like `map`, `zip`, `takeN`, `fold` ... etc. +Also, you can use only `ReadChannel` or `WriteChannel` interfaces, where an appropriative read/write operations are defined. +For `ReadChannel`, exists usual stream functions, like `map`, `zip`, `takeN`, `fold` ... etc. -For example, let we have the direct port of golang code: +For example, let we have the direct analouf of golang code: ~~~ scala val channel = gopher.makeChannel[Int](100) @@ -92,19 +96,37 @@ val consumer = async { ~~~ -last loop can be repharased in more scala wat as: +last loop can be repharased in more scala way as: ~~~ scala val sum = (channel.take(1000)).fold(0)((s,i) => s+i) ~~~ + + Here is filtered channel, wich produce prime numbers: + +~~~ scala + def filter0(in:Channel[Future,Int,Int]):ReadChannel[Future,Int] = + val filtered = makeChannel[Int]() + var proxy: ReadChannel[Future, Int] = in; + async { + while(true) { + val prime = proxy.read() + proxy = proxy.filter(_ % prime != 0) + filtered.write(prime) + } + } + filtered +~~~ + + Scala Iterable can be represented as `ReadChannel` via extension method `asReadChannel`. Also, we can use Scala futures as channels, which produce one value and then closes. For obtaining such input use `gopherApi.futureInput`. `|` (i.e. or) operator used for merged inputs, i.e. `(x|y).read` will read a value from channel x or y when one will be available. -Also, note that you can provide own Input and Output implementations by implementing callback `cbread` and `cbwrite` methods. +Also, note that you can provide own ReadChannel and WriteChannel implementations by implementing ```addReader/addWriter``` methods. ## Select loops and folds @@ -113,8 +135,6 @@ Also, note that you can provide own Input and Output implementations by implemen from a set of blocking operations select one who is ready to input/output and run it. The usual pattern of channel processing in go language is to wrap select operation into an endless loop. - - Gopher provides similar functionality: ~~~ scala async[Future]{ @@ -134,29 +154,64 @@ async[Future]{ * `v:channel.write if (v==expr)` (for writing `expr` into channel). * `v:Time.after if (v==expr)` (for timeouts). - Inside case actions, we can use blocking read/writes and await operations. - - Example: ~~~ scala -TODO +async{ + var done = false + while(!done) { + select { + case x: ch.read => + sum = sum+x + if (x > 100) { + done = true + } + } + } +} +~~~ + + select.loop can be used for less imperative code organization: + +~~~ scala +async{ + select.loop{ + case x: ch.read => + sum = sum+x + (x <= 100) + } +} ~~~ - A combination of variable and select loop better modeled with help 'fold over select' construction: + Here, the branch inside select should return true or false. If true -- loop is continued, if false - interrupted. + + + select.fold (or afold - as variant which is alredy wrapped in async) provide an abstraction for iterating over set of + events by applying function to state: ~~~ scala -TODO +def filter1(in:Channel[Future,Int,Int]):ReadChannel[Future,Int] = + val q = makeChannel[Int]() + val filtered = makeChannel[Int]() + select.afold(in){ (ch, s) => + s.select{ + case prime: ch.read => + filtered.write(prime) + ch.filter(_ % prime != 0) + } + } + filtered ~~~ + The argument to the fold function is state and special 'select group' marker, for which we can call `select` statement. + Function should or produce next state (as in `filter1` example) or produce special value SelectFold.Done(x): - More than one variables in state can be modeled with partial function case syntax: ~~~ scala val fib = select.fold((0,1)) { case ((x,y), s) => - s match { + s.select{ case x:channel.write => (y,y+x) - case q:quit.read => select.exit((x,y)) + case q:quit.read => SelectFold.Done((x,y)) } } ~~~ @@ -173,7 +228,7 @@ val multiplexed = select amap { ## Done signals. - Sometimes it is useful to receive a message when some `Input` becomes closed. Such inputs are named 'CloseableInputs' and provides a way to receive close notification in selector using `done` pseudo-type. + Sometimes it is useful to receive a message when some `ReadChannel` becomes closed. Such inputs are named 'CloseableInputs' and provides a way to receive close notification in selector using `done` pseudo-type. ~~~ scala while(!done) @@ -184,37 +239,6 @@ val multiplexed = select amap { } ~~~ - Note, that you must exit from current flow in `done` handler, otherwise `done` signals will be intensively generated in a loop. - - -## Effected{Input,Output,Channel} - - One useful programming pattern, often used in CSP-style programming: have a channel from wich we read (or to where we write) as a part of a state. In Go language, this is usually modelled as a mutable variable, changed inside the same select statement, where one is read/written. - - In scala-gopher, we have the ability to use a technique of 'EffectedChannel', which can be seen as an entity, which holds channel, can be used in read/write and can be changed only via effect (operation, which accepts the previous state and returns the next). - -Let's look at the example: - -~~~ scala - def generate(n:Int, quit:Promise[Boolean]):Channel[Int] = - { - val channel = makeChannel[Int]() - channel.awriteAll(2 to n) andThen (_ => quit success true) - channel - } - - def filter(in:Channel[Int]):Input[Int] = - { - val filtered = makeChannel[Int]() - val sieve = makeEffectedInput(in) - sieve.aforeach { prime => - sieve <<= (_.filter(_ % prime != 0)) - filtered <~ prime - } - filtered - } -~~~ -Here in 'filter', we generate a set of prime numbers, and make a sieve of Eratosthenes by sequentially applying 'filter' effect to state of sieve EffectedInput. diff --git a/js/src/main/scala/gopher/JSGopher.scala b/js/src/main/scala/gopher/JSGopher.scala index 98690006..5cc73cff 100644 --- a/js/src/main/scala/gopher/JSGopher.scala +++ b/js/src/main/scala/gopher/JSGopher.scala @@ -45,3 +45,6 @@ object JSGopher extends GopherAPI: val timer = new Timer("gopher") + +val Gopher = JSGopher + diff --git a/jvm/src/main/scala/gopher/JVMGopher.scala b/jvm/src/main/scala/gopher/JVMGopher.scala index b497479d..77763367 100644 --- a/jvm/src/main/scala/gopher/JVMGopher.scala +++ b/jvm/src/main/scala/gopher/JVMGopher.scala @@ -70,4 +70,6 @@ object JVMGopher extends GopherAPI: } else { logger.log(level, message, ex) } - \ No newline at end of file + + +val Gopher = JVMGopher diff --git a/shared/src/main/scala/gopher/GopherAPI.scala b/shared/src/main/scala/gopher/GopherAPI.scala index 7b79078c..894e8b39 100644 --- a/shared/src/main/scala/gopher/GopherAPI.scala +++ b/shared/src/main/scala/gopher/GopherAPI.scala @@ -9,7 +9,6 @@ case object DefaultGopherConfig extends GopherConfig trait GopherAPI: - def apply[F[_]:CpsSchedulingMonad](cfg:GopherConfig = DefaultGopherConfig): Gopher[F] /** diff --git a/shared/src/test/scala/gopher/channels/SelectSuite.scala b/shared/src/test/scala/gopher/channels/SelectSuite.scala index 7d11c2bb..60200000 100644 --- a/shared/src/test/scala/gopher/channels/SelectSuite.scala +++ b/shared/src/test/scala/gopher/channels/SelectSuite.scala @@ -171,8 +171,6 @@ class SelectSuite extends FunSuite } } - - test("basic compound select with loop select syntax") { From cd49b6cb49ec9b833725f2de10e420e4cf6069cb Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Sun, 28 Mar 2021 14:36:16 +0300 Subject: [PATCH 061/161] Simplicify Select.Fold interface --- .../main/scala/gopher/impl/BaseChannel.scala | 2 + .../scala/gopher/impl/PromiseChannel.scala | 22 ++++---- .../impl/GuardedSPSCUnbufferedChannel.scala | 12 ++--- .../scala/gopher/impl/PromiseChannel.scala | 10 +++- shared/src/main/scala/gopher/Select.scala | 42 ++++++++------- .../src/main/scala/gopher/SelectGroup.scala | 2 +- .../scala/examples/FibonaccyFoldSuite.scala | 4 +- shared/src/test/scala/examples/Sieve.scala | 3 +- .../src/test/scala/examples/SieveSuite.scala | 4 +- .../gopher/channels/FoldSelectSuite.scala | 10 ++-- .../gopher/channels/MacroSelectSuite.scala | 51 ++++++++++--------- .../gopher/channels/SelectErrorSuite.scala | 4 +- .../gopher/channels/SelectTimeoutSuite.scala | 4 +- .../channels/UnbufferedSelectSuite.scala | 4 +- 14 files changed, 88 insertions(+), 86 deletions(-) diff --git a/js/src/main/scala/gopher/impl/BaseChannel.scala b/js/src/main/scala/gopher/impl/BaseChannel.scala index 3bf2ef51..e40c2250 100644 --- a/js/src/main/scala/gopher/impl/BaseChannel.scala +++ b/js/src/main/scala/gopher/impl/BaseChannel.scala @@ -65,6 +65,7 @@ abstract class BaseChannel[F[_],A](override val gopherApi: JSGopher[F]) extends process() } else { doneReaders.enqueue(reader) + process() } protected def processClose(): Unit = @@ -91,6 +92,7 @@ abstract class BaseChannel[F[_],A](override val gopherApi: JSGopher[F]) extends protected def processCloseDone(): Unit = val success = Success(()) exhauseQueue(doneReaders, f => f(success)) + protected def processCloseReaders(): Unit = val channelClosed = Failure(ChannelClosedException()) diff --git a/js/src/main/scala/gopher/impl/PromiseChannel.scala b/js/src/main/scala/gopher/impl/PromiseChannel.scala index e02cd648..08b0721d 100644 --- a/js/src/main/scala/gopher/impl/PromiseChannel.scala +++ b/js/src/main/scala/gopher/impl/PromiseChannel.scala @@ -10,8 +10,11 @@ import scala.util.control.NonFatal class PromiseChannel[F[_]:CpsAsyncMonad, A](gopherApi: JSGopher[F]) extends BaseChannel[F,A](gopherApi): private var value: Option[A] = None + private var readed = false + + protected def isEmpty: Boolean = value.isEmpty || readed - protected def isEmpty: Boolean = value.isEmpty + //override def addDoneReader(reader: Reader[Unit]): Unit = protected def process(): Unit = var done = false @@ -32,33 +35,26 @@ class PromiseChannel[F[_]:CpsAsyncMonad, A](gopherApi: JSGopher[F]) extends Base throw new DeadlockDetected() } if (!readers.isEmpty && value.isDefined) { - var readed = false - while(!readers.isEmpty) { + while(!readers.isEmpty && !readed) { val r = readers.dequeue() if (!r.isExpired) then r.capture() match case Some(f) => - if (!readed) then r.markUsed() submitTask(()=>f(Success(value.get))) readed = true - else - r.markFree() - processCloseDone() // to have .done call befor channel-closed. - if (!r.isExpired) then - r.capture().foreach{ f => - r.markUsed() - submitTask(()=>f(Failure(ChannelClosedException()))) - } case None => if (!r.isExpired) throw new DeadlockDetected() } + //if (readed) { + // processCloseDone() + //} } if (closed) then processClose() - + \ No newline at end of file diff --git a/jvm/src/main/scala/gopher/impl/GuardedSPSCUnbufferedChannel.scala b/jvm/src/main/scala/gopher/impl/GuardedSPSCUnbufferedChannel.scala index ad310851..a439989c 100644 --- a/jvm/src/main/scala/gopher/impl/GuardedSPSCUnbufferedChannel.scala +++ b/jvm/src/main/scala/gopher/impl/GuardedSPSCUnbufferedChannel.scala @@ -56,13 +56,11 @@ class GuardedSPSCUnbufferedChannel[F[_]:CpsAsyncMonad,A]( } } if (isClosed && (readers.isEmpty || writers.isEmpty) ) then - progress |= ( - processWriteClose() - || - processReadClose() - || - processDoneClose() - ) + progress |= processWriteClose() + while(! doneReaders.isEmpty) { + progress |= processDoneClose() + } + progress |= processReadClose() if (!progress) then if !checkLeaveStep() then progress = true diff --git a/jvm/src/main/scala/gopher/impl/PromiseChannel.scala b/jvm/src/main/scala/gopher/impl/PromiseChannel.scala index 22be60c2..bb048e99 100644 --- a/jvm/src/main/scala/gopher/impl/PromiseChannel.scala +++ b/jvm/src/main/scala/gopher/impl/PromiseChannel.scala @@ -84,12 +84,18 @@ import scala.util.Failure r.capture() match case Some(f) => done = true - r.markUsed() if (readed.compareAndSet(false,true)) then + r.markUsed() val a = ar.nn.asInstanceOf[A] taskExecutor.execute(() => f(Success(a))) else - taskExecutor.execute(() => f(Failure(new ChannelClosedException()))) + // before throw channel-close exception, let's check + if (doneReaders.isEmpty) then + r.markUsed() + taskExecutor.execute(() => f(Failure(new ChannelClosedException()))) + else + r.markFree() + readers.addLast(r) // called later after done case None => if (!r.isExpired) { if (readers.isEmpty) diff --git a/shared/src/main/scala/gopher/Select.scala b/shared/src/main/scala/gopher/Select.scala index f6054528..905deb1f 100644 --- a/shared/src/main/scala/gopher/Select.scala +++ b/shared/src/main/scala/gopher/Select.scala @@ -20,39 +20,37 @@ class Select[F[_]](api: Gopher[F]): def loop: SelectLoop[F] = new SelectLoop[F](api) - def fold[S](s0:S)(step: (S,SelectGroup[F,S|SelectFold.Done[S]])=> S | SelectFold.Done[S]): S = { - var s: S = s0 - while{ - val g = SelectGroup[F,S|SelectFold.Done[S]](api) - step(s,g) match { - case SelectFold.Done(r:S) => - s=r - false - case other => - s=other.asInstanceOf[S] - true - } - } do () - s + + def fold[S](s0:S)(step: S => S | SelectFold.Done[S]): S = { + var s: S = s0 + while{ + step(s) match + case SelectFold.Done(r) => + s = r.asInstanceOf[S] + false + case other => + s = other.asInstanceOf[S] + true + } do () + s } - def fold_async[S](s0:S)(step: (S,SelectGroup[F,S|SelectFold.Done[S]])=> F[S | SelectFold.Done[S]]): F[S] = { - var g = SelectGroup[F,S|SelectFold.Done[S]](api) - api.asyncMonad.flatMap(step(s0,g)){ s => - s match + def fold_async[S](s0:S)(step: S => F[S | SelectFold.Done[S]]): F[S] = { + api.asyncMonad.flatMap(step(s0)){ s => + s match case SelectFold.Done(r) => api.asyncMonad.pure(r.asInstanceOf[S]) case other => fold_async[S](other.asInstanceOf[S])(step) } } - - transparent inline def afold[S](s0:S)(inline step: (S,SelectGroup[F,S|SelectFold.Done[S]])=> S | SelectFold.Done[S]) : F[S] = + + transparent inline def afold[S](s0:S)(inline step: S => S | SelectFold.Done[S]) : F[S] = async[F](using api.asyncMonad).apply{ fold(s0)(step) } - def afold_async[S](s0:S)(step: (S,SelectGroup[F,S|SelectFold.Done[S]])=> F[S | SelectFold.Done[S]]) : F[S] = + def afold_async[S](s0:S)(step: S => F[S | SelectFold.Done[S]]) : F[S] = fold_async(s0)(step) - + //def map[A](step: PartialFunction[SelectGroup[F,A],A|SelectFold.Done[Unit]]): ReadChannel[F,A] = diff --git a/shared/src/main/scala/gopher/SelectGroup.scala b/shared/src/main/scala/gopher/SelectGroup.scala index 0a3d7fc6..3ed8c797 100644 --- a/shared/src/main/scala/gopher/SelectGroup.scala +++ b/shared/src/main/scala/gopher/SelectGroup.scala @@ -152,7 +152,7 @@ class SelectGroup[F[_], S](api: Gopher[F]) extends SelectListeners[F,S,S]: trait Expiration: def canExpire: Boolean = true def isExpired: Boolean = waitState.get()==2 - def markUsed(): Unit = waitState.lazySet(2) + def markUsed(): Unit = waitState.set(2) def markFree(): Unit = { waitState.set(0) } diff --git a/shared/src/test/scala/examples/FibonaccyFoldSuite.scala b/shared/src/test/scala/examples/FibonaccyFoldSuite.scala index fe48b58a..8138dd5f 100644 --- a/shared/src/test/scala/examples/FibonaccyFoldSuite.scala +++ b/shared/src/test/scala/examples/FibonaccyFoldSuite.scala @@ -22,8 +22,8 @@ object FibonaccyFold { def fibonacci(c: WriteChannel[Future,Long], quit: ReadChannel[Future,Int]): Future[(Long,Long)] = async { - select.fold((0L,1L)) { case ((x,y),s) => - s.select{ + select.fold((0L,1L)) { case (x,y) => + select{ case wx: c.write if wx == x => (y, x+y) case q: quit.read => SelectFold.Done((x,y)) diff --git a/shared/src/test/scala/examples/Sieve.scala b/shared/src/test/scala/examples/Sieve.scala index a57ddf66..77f1669b 100644 --- a/shared/src/test/scala/examples/Sieve.scala +++ b/shared/src/test/scala/examples/Sieve.scala @@ -34,8 +34,7 @@ object Sieve1 { def runFold(in:Channel[Future,Int, Int], out: Channel[Future,Int, Int]) = async { // ??? - - select.fold(in){ (s, g) => + select.fold(in){ s => val x = s.read() out.write(x) s.filter(_ % x != 0) diff --git a/shared/src/test/scala/examples/SieveSuite.scala b/shared/src/test/scala/examples/SieveSuite.scala index 74b5c3be..9ea58d02 100644 --- a/shared/src/test/scala/examples/SieveSuite.scala +++ b/shared/src/test/scala/examples/SieveSuite.scala @@ -50,8 +50,8 @@ object Sieve { val q = makeChannel[Int]() val filtered = makeChannel[Int]() - select.afold(in){ (ch, s) => - s.select{ + select.afold(in){ ch => + select{ case prime: ch.read => filtered.write(prime) ch.filter(_ % prime != 0) diff --git a/shared/src/test/scala/gopher/channels/FoldSelectSuite.scala b/shared/src/test/scala/gopher/channels/FoldSelectSuite.scala index 31f22de7..bea1a2a4 100644 --- a/shared/src/test/scala/gopher/channels/FoldSelectSuite.scala +++ b/shared/src/test/scala/gopher/channels/FoldSelectSuite.scala @@ -26,8 +26,8 @@ class FoldSelectSuite extends FunSuite // dotty bug, val generator = async { - select.fold(in){ (ch,s) => - s.select{ + select.fold(in){ ch => + select{ case p: ch.read => r0 = r0 :+ p out.write(p) @@ -63,11 +63,11 @@ class FoldSelectSuite extends FunSuite val quit = makeChannel[Boolean]() val generator = async { - select.fold((in1,in2,0)){ case ((in1,in2,n),s) => - s select { + select.fold((in1,in2,0)){ case (in1,in2,n) => + select { case x:in1.read => if (x >= 100) { - s.done((in1, in2, n)) + SelectFold.Done((in1, in2, n)) } else { (in2, in1, n + x) } diff --git a/shared/src/test/scala/gopher/channels/MacroSelectSuite.scala b/shared/src/test/scala/gopher/channels/MacroSelectSuite.scala index a25a1c77..4b872d8a 100644 --- a/shared/src/test/scala/gopher/channels/MacroSelectSuite.scala +++ b/shared/src/test/scala/gopher/channels/MacroSelectSuite.scala @@ -263,12 +263,12 @@ class MacroSelectSuite extends FunSuite val quit = Promise[Boolean]() val quitChannel = futureInput(quit.future) val r = async { - select.fold(0){ (x,g) => - g.select { + select.fold(0){ x => + select { case a:ch.read => back <~ a x+a case q: quitChannel.read => - g.done(x) + SelectFold.Done(x) } } } @@ -290,12 +290,12 @@ class MacroSelectSuite extends FunSuite //implicit val debugLevel = cps.macroFlags.DebugLevel(20) for { _ <- Future.successful(()) - sf = select.afold((0, 0, 0)) { case ((n1, n2, nIdle), s) => - s.select{ + sf = select.afold((0, 0, 0)) { case (n1, n2, nIdle) => + select{ case x: ch1.read => val nn1 = n1 + 1 if (nn1 > 100) { - s.done((nn1, n2, nIdle)) + SelectFold.Done((nn1, n2, nIdle)) } else { ch2.write(x) (nn1, n2, nIdle) @@ -309,14 +309,14 @@ class MacroSelectSuite extends FunSuite } (n1, n2, ni) <- sf _ = assert(n1 + n2 + ni > 100) - sf2 = select.afold((0, 0)) { case ((n1, nIdle), s) => - s.select{ + sf2 = select.afold((0, 0)) { case (n1, nIdle) => + select{ case x: ch1.read => (n1 + 1, nIdle) case t: Time.after if t == (50 milliseconds) => val nni = nIdle + 1 if (nni > 3) { - s.done((n1, nni)) + SelectFold.Done((n1, nni)) } else { (n1, nni) } @@ -328,7 +328,7 @@ class MacroSelectSuite extends FunSuite } - test("map over selector".only) { + test("map over selector") { val ch1 = makeChannel[Int](10) val ch2 = makeChannel[Int](10) val quit = Promise[Boolean]() @@ -409,10 +409,12 @@ class MacroSelectSuite extends FunSuite test("check for done signal from one-time channel") { val ch = makeOnceChannel[Int]() - val sf = select.afold((0)){ (x,s) => - s.select{ - case v: ch.read => x + v - case v: ch.done.read => s.done(x) + val sf = select.afold((0)){ x => + select{ + case v: ch.read => + x + v + case v: ch.done.read => + SelectFold.Done(x) } } val f1 = ch.awrite(1) @@ -425,10 +427,10 @@ class MacroSelectSuite extends FunSuite test("check for done signal from unbuffered channel") { val ch = makeChannel[Int]() - val sf = select.afold((0)){ (x,s) => - s.select{ + val sf = select.afold((0)){ x => + select{ case v: ch.read => x + v - case v: ch.done.read => s.done(x) + case v: ch.done.read => SelectFold.Done(x) } } val f1 = ch.awriteAll(1 to 5) map (_ =>ch.close) @@ -441,10 +443,10 @@ class MacroSelectSuite extends FunSuite test("check for done signal from buffered channel") { val ch = makeChannel[Int](10) - val sf = select.afold((0)){ (x,s) => - s.select { + val sf = select.afold((0)){ x => + select { case v: ch.read => x + v - case c: ch.done.read => s.done(x) + case c: ch.done.read => SelectFold.Done(x) } } val f1 = async { @@ -473,12 +475,12 @@ class MacroSelectSuite extends FunSuite throw ChannelClosedException() } } - val chs2 = select.afold(0){ (n,s) => - s.select{ + val chs2 = select.afold(0){ n => + select{ case x:chs.read => n + x case x:chs.done.read => - s.done(n) + SelectFold.Done(n) } } // note, that if we want call of quit after last write, @@ -487,7 +489,8 @@ class MacroSelectSuite extends FunSuite _ <- ch2.awriteAll(1 to 10) _ <- q.awrite(true) } yield 1 val r = await(chs2) - assert( r == (1 to 10).map(_ * 5).sum + 1) + + assert( r == (1 to 10).map(_ * 5).sum ) } } diff --git a/shared/src/test/scala/gopher/channels/SelectErrorSuite.scala b/shared/src/test/scala/gopher/channels/SelectErrorSuite.scala index c7147c39..6fe887f1 100644 --- a/shared/src/test/scala/gopher/channels/SelectErrorSuite.scala +++ b/shared/src/test/scala/gopher/channels/SelectErrorSuite.scala @@ -145,9 +145,9 @@ class SelectErrorSuite extends FunSuite var svEx: Throwable = null val g = - select.afold((ch1,ch2,0,List[Int]())) { case ((x,y,z,l),s) => + select.afold((ch1,ch2,0,List[Int]())) { case (x,y,z,l) => try { - s.apply{ + select{ case z1: ch3.read => if (z1==10) { throw new RuntimeException("Be-be-be!") diff --git a/shared/src/test/scala/gopher/channels/SelectTimeoutSuite.scala b/shared/src/test/scala/gopher/channels/SelectTimeoutSuite.scala index 0d016fa0..fd2fe7de 100644 --- a/shared/src/test/scala/gopher/channels/SelectTimeoutSuite.scala +++ b/shared/src/test/scala/gopher/channels/SelectTimeoutSuite.scala @@ -83,8 +83,8 @@ class SelectTimeoutSuite extends FunSuite test("timeout in select.fold") { val ch1 = makeChannel[Int](10) val f = async { - select.fold(0) { (state,sl) => - sl.apply{ + select.fold(0) { state => + select{ case x: ch1.read => state+1 case x: Time.after if (x == 100.milliseconds) => SelectFold.Done((state+10)) diff --git a/shared/src/test/scala/gopher/channels/UnbufferedSelectSuite.scala b/shared/src/test/scala/gopher/channels/UnbufferedSelectSuite.scala index 5da0c3d7..030732bd 100644 --- a/shared/src/test/scala/gopher/channels/UnbufferedSelectSuite.scala +++ b/shared/src/test/scala/gopher/channels/UnbufferedSelectSuite.scala @@ -43,8 +43,8 @@ class UnbufferedSelectSuite extends FunSuite val quit = Promise[Boolean]() val quitChannel = quit.future.asChannel val r = async { - select.fold(0){ (x,s) => - s.apply{ + select.fold(0){ x => + select{ case a:ch.read => x+a case q: quitChannel.read => SelectFold.Done(x) } From fb03afa94443029d16d730eabfe9b962c3f61d2c Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Sun, 28 Mar 2021 14:40:07 +0300 Subject: [PATCH 062/161] simplify documentation --- README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index c4288041..83fa2d20 100644 --- a/README.md +++ b/README.md @@ -183,7 +183,7 @@ async{ } ~~~ - Here, the branch inside select should return true or false. If true -- loop is continued, if false - interrupted. + Here, the branch inside select should return true or false. If true -- loop will be continued, if false - finished. select.fold (or afold - as variant which is alredy wrapped in async) provide an abstraction for iterating over set of @@ -193,8 +193,8 @@ async{ def filter1(in:Channel[Future,Int,Int]):ReadChannel[Future,Int] = val q = makeChannel[Int]() val filtered = makeChannel[Int]() - select.afold(in){ (ch, s) => - s.select{ + select.afold(in){ ch => + select{ case prime: ch.read => filtered.write(prime) ch.filter(_ % prime != 0) @@ -203,13 +203,13 @@ def filter1(in:Channel[Future,Int,Int]):ReadChannel[Future,Int] = filtered ~~~ - The argument to the fold function is state and special 'select group' marker, for which we can call `select` statement. + The argument to the fold function is state. Function should or produce next state (as in `filter1` example) or produce special value SelectFold.Done(x): ~~~ scala -val fib = select.fold((0,1)) { case ((x,y), s) => - s.select{ +val fib = select.fold((0,1)) { case (x,y) => + select{ case x:channel.write => (y,y+x) case q:quit.read => SelectFold.Done((x,y)) } From 9fd74d765e22118e5cd802214355a864f81f3c00 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Wed, 31 Mar 2021 07:18:18 +0300 Subject: [PATCH 063/161] 2.0.0 - prereelase --- build.sbt | 14 +++---- project/build.properties | 2 +- publish.sbt | 39 +++++++++++++++++++ .../src/main/scala/gopher/ReadChannel.scala | 3 ++ 4 files changed, 50 insertions(+), 8 deletions(-) create mode 100644 publish.sbt diff --git a/build.sbt b/build.sbt index 9fe7dadc..e55d8c6d 100644 --- a/build.sbt +++ b/build.sbt @@ -1,16 +1,16 @@ -val dottyVersion = "3.0.0-RC2-bin-SNAPSHOT" -//val dottyVersion = "3.0.0-RC1" +//val dottyVersion = "3.0.0-RC2-bin-SNAPSHOT" +val dottyVersion = "3.0.0-RC2" //val dottyVersion = dottyLatestNightlyBuild.get val sharedSettings = Seq( - version := "2.0.0-SNAPSHOT", + version := "2.0.0-RC2", organization := "com.github.rssh", scalaVersion := dottyVersion, name := "scala-gopher", resolvers += "Local Ivy Repository" at "file://"+Path.userHome.absolutePath+"/.ivy2/local", - libraryDependencies += "com.github.rssh" %%% "dotty-cps-async" % "0.5.0-SNAPSHOT", - libraryDependencies += "org.scalameta" %%% "munit" % "0.7.22+7-1e1017a3+20210228-1512-SNAPSHOT" % Test, + libraryDependencies += "com.github.rssh" %%% "dotty-cps-async" % "0.5.0", + libraryDependencies += "org.scalameta" %%% "munit" % "0.7.23" % Test, testFrameworks += new TestFramework("munit.Framework") ) @@ -19,7 +19,7 @@ lazy val root = project .aggregate(gopher.js, gopher.jvm) .settings( Sphinx / sourceDirectory := baseDirectory.value / "docs", - git.remoteRepo := "git@github.com:rssh/dotty-cps-async.git", + git.remoteRepo := "git@github.com:rssh/scala-gopher.git", publishArtifact := false, ).enablePlugins(SphinxPlugin) .enablePlugins(GhpagesPlugin) @@ -34,7 +34,7 @@ lazy val gopher = crossProject(JSPlatform, JVMPlatform) ).jsSettings( libraryDependencies += ("org.scala-js" %%% "scalajs-java-logging" % "1.0.0").withDottyCompat(scalaVersion.value), // TODO: switch to ModuleES ? - scalaJSLinkerConfig ~= { _.withModuleKind(ModuleKind.CommonJSModule) }, + // scalaJSLinkerConfig ~= { _.withModuleKind(ModuleKind.CommonJSModule) }, scalaJSUseMainModuleInitializer := true, ) diff --git a/project/build.properties b/project/build.properties index c06db1bb..dbae93bc 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=1.4.5 +sbt.version=1.4.9 diff --git a/publish.sbt b/publish.sbt new file mode 100644 index 00000000..cc5dbc4e --- /dev/null +++ b/publish.sbt @@ -0,0 +1,39 @@ +credentials += Credentials(Path.userHome / ".sbt" / "sonatype_credentials") + +ThisBuild / organization := "com.github.rssh" +ThisBuild / organizationName := "rssh" +ThisBuild / organizationHomepage := Some(url("https://github.com/rssh")) + +ThisBuild / scmInfo := Some( + ScmInfo( + url("https://github.com/rssh/scala-gopher"), + "scm:git@github.com:rssh/scala-gopher.git" + ) +) + + +ThisBuild / developers := List( + Developer( + id = "rssh", + name = "Ruslan Shevchenko", + email = "ruslan@shevchenko.kiev.ua", + url = url("https://github.com/rssh") + ) +) + + +ThisBuild / description := "scala-gopher: asynchronous implementation of CSP ( go-like channels/selectors ) in scala " +ThisBuild / licenses := List("Apache 2" -> new URL("http://www.apache.org/licenses/LICENSE-2.0.txt")) +ThisBuild / homepage := Some(url("https://github.com/rssh/scala-gopher")) + +ThisBuild / pomIncludeRepository := { _ => false } +ThisBuild / publishTo := { + val nexus = "https://oss.sonatype.org/" + if (isSnapshot.value) Some("snapshots" at nexus + "content/repositories/snapshots") + else Some("releases" at nexus + "service/local/staging/deploy/maven2") +} +ThisBuild / publishMavenStyle := true + + + + diff --git a/shared/src/main/scala/gopher/ReadChannel.scala b/shared/src/main/scala/gopher/ReadChannel.scala index 1b034a9b..811c4e76 100644 --- a/shared/src/main/scala/gopher/ReadChannel.scala +++ b/shared/src/main/scala/gopher/ReadChannel.scala @@ -208,6 +208,9 @@ object ReadChannel: def fromFuture[F[_],A](f: F[A])(using Gopher[F]): ReadChannel[F,A] = futureInput(f) + def fromValues[F[_],A](values: A*)(using Gopher[F]): ReadChannel[F,A] = + fromIterable(values) + end ReadChannel From 29926df3b774a4e37d164cdcbea7379f9805a8d8 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Wed, 31 Mar 2021 07:21:16 +0300 Subject: [PATCH 064/161] 0.99x will live in own branch --- 0.99.x/README.md | 542 ------------ 0.99.x/build.sbt | 71 -- 0.99.x/project/plugins.sbt | 3 - .../scala/gopher/ChannelClosedException.scala | 10 - 0.99.x/src/main/scala/gopher/Defers.scala | 163 ---- .../main/scala/gopher/FlowTermination.scala | 51 -- 0.99.x/src/main/scala/gopher/Gopher.scala | 78 -- 0.99.x/src/main/scala/gopher/GopherAPI.scala | 215 ----- .../main/scala/gopher/ThreadingPolicy.scala | 10 - 0.99.x/src/main/scala/gopher/Time.scala | 91 -- 0.99.x/src/main/scala/gopher/Transputer.scala | 448 ---------- .../gopher/channels/ActorBackedChannel.scala | 111 --- .../channels/BaseBufferedChannelActor.scala | 53 -- .../channels/BufferedChannelActor.scala | 111 --- .../main/scala/gopher/channels/Channel.scala | 65 -- .../scala/gopher/channels/ChannelActor.scala | 112 --- .../gopher/channels/ChannelActorMessage.scala | 54 -- .../gopher/channels/ChannelProcessor.scala | 42 - .../gopher/channels/ChannelSupervisor.scala | 26 - .../gopher/channels/CloseableInput.scala | 35 - .../scala/gopher/channels/Continuated.scala | 134 --- .../channels/CurrentFlowTermination.scala | 46 - .../scala/gopher/channels/DoneProvider.scala | 16 - .../gopher/channels/EffectedChannel.scala | 84 -- .../scala/gopher/channels/EffectedInput.scala | 39 - .../gopher/channels/EffectedOutput.scala | 38 - .../scala/gopher/channels/ExpireChannel.scala | 82 -- .../gopher/channels/FoldSelectorBuilder.scala | 802 ------------------ .../channels/ForeverSelectorBuilder.scala | 122 --- .../scala/gopher/channels/FutureInput.scala | 65 -- .../gopher/channels/GopherAPIProvider.scala | 8 - .../channels/GrowingBufferedChannel.scala | 79 -- .../main/scala/gopher/channels/Input.scala | 591 ------------- .../scala/gopher/channels/InputOutput.scala | 155 ---- .../channels/InputSelectorBuilder.scala | 119 --- .../gopher/channels/InputWithTimeouts.scala | 52 -- .../scala/gopher/channels/LazyChannel.scala | 24 - .../gopher/channels/OnceSelectorBuilder.scala | 87 -- .../gopher/channels/OneTimeChannel.scala | 76 -- .../main/scala/gopher/channels/OrInput.scala | 62 -- .../main/scala/gopher/channels/Output.scala | 172 ---- .../gopher/channels/OutputWithTimeouts.scala | 53 -- .../channels/PromiseFlowTermination.scala | 50 -- .../main/scala/gopher/channels/Selector.scala | 297 ------- .../gopher/channels/SelectorArguments.scala | 131 --- .../gopher/channels/SelectorBuilder.scala | 635 -------------- .../gopher/channels/SelectorFactory.scala | 100 --- .../channels/UnbufferedChannelActor.scala | 133 --- .../scala/gopher/channels/ZippedInput.scala | 116 --- .../main/scala/gopher/channels/package.scala | 27 - .../scala/gopher/goasync/AsyncApply.scala | 146 ---- .../scala/gopher/goasync/AsyncIterable.scala | 64 -- .../scala/gopher/goasync/AsyncOption.scala | 63 -- .../scala/gopher/goasync/AsyncWrapper.scala | 35 - .../main/scala/gopher/goasync/GoAsync.scala | 287 ------- 0.99.x/src/main/scala/gopher/package.scala | 198 ----- .../transputers/ReplicateTransputer.scala | 286 ------- .../transputers/TransputerSupervisor.scala | 86 -- .../scala/gopher/transputers/package.scala | 12 - .../main/scala/gopher/util/ASTUtilImpl.scala | 21 - .../src/main/scala/gopher/util/Effected.scala | 62 -- .../scala/gopher/util/IntIndexedReverse.scala | 78 -- .../main/scala/gopher/util/MacroUtil.scala | 138 --- .../main/scala/gopher/util/ReflectUtil.scala | 59 -- 0.99.x/src/test/resources/application.conf | 6 - .../test/scala/example/BetterSieveSuite.scala | 64 -- 0.99.x/src/test/scala/example/Bingo.scala | 70 -- .../test/scala/example/BroadcasterSuite.scala | 179 ---- 0.99.x/src/test/scala/example/CopyFile.scala | 25 - .../scala/example/FibonaccyAsyncSuite.scala | 76 -- .../FibonaccyAsyncUnsugaredSuite.scala | 81 -- .../test/scala/example/FibonaccySuite.scala | 61 -- .../gopher/channels/ChannelCleanupSuite.scala | 79 -- .../gopher/channels/CommonTestObjects.scala | 30 - .../channels/SchedulerStartupTest.scala | 31 - .../hofasyn/FibbonacyAsyncLoopSuite.scala | 66 -- .../scala/gopher/hofasyn/HofAsyncSuite.scala | 146 ---- .../gopher/internal/FoldParseSuite.scala | 85 -- .../gopher/obsolete/IOComposeSuite.scala | 75 -- .../test/scala/gopher/scope/DefersSuite.scala | 85 -- .../scala/gopher/scope/GoWithDeferSuite.scala | 50 -- .../scala/gopher/scope/ScopeMacroSuite.scala | 71 -- .../gopher/scope/SimpleStatementSuite.scala | 57 -- 0.99.x/src/test/scala/gopher/tags/Gen.scala | 5 - 0.99.x/src/test/scala/gopher/tags/Now.scala | 6 - .../test/scala/gopher/time/TimeSuite.scala | 29 - .../gopher/transputers/ReplicateSuite.scala | 182 ---- .../transputers/TransputerRestartTest.scala | 153 ---- 88 files changed, 9933 deletions(-) delete mode 100644 0.99.x/README.md delete mode 100644 0.99.x/build.sbt delete mode 100644 0.99.x/project/plugins.sbt delete mode 100644 0.99.x/src/main/scala/gopher/ChannelClosedException.scala delete mode 100644 0.99.x/src/main/scala/gopher/Defers.scala delete mode 100644 0.99.x/src/main/scala/gopher/FlowTermination.scala delete mode 100644 0.99.x/src/main/scala/gopher/Gopher.scala delete mode 100644 0.99.x/src/main/scala/gopher/GopherAPI.scala delete mode 100644 0.99.x/src/main/scala/gopher/ThreadingPolicy.scala delete mode 100644 0.99.x/src/main/scala/gopher/Time.scala delete mode 100644 0.99.x/src/main/scala/gopher/Transputer.scala delete mode 100644 0.99.x/src/main/scala/gopher/channels/ActorBackedChannel.scala delete mode 100644 0.99.x/src/main/scala/gopher/channels/BaseBufferedChannelActor.scala delete mode 100644 0.99.x/src/main/scala/gopher/channels/BufferedChannelActor.scala delete mode 100644 0.99.x/src/main/scala/gopher/channels/Channel.scala delete mode 100644 0.99.x/src/main/scala/gopher/channels/ChannelActor.scala delete mode 100644 0.99.x/src/main/scala/gopher/channels/ChannelActorMessage.scala delete mode 100644 0.99.x/src/main/scala/gopher/channels/ChannelProcessor.scala delete mode 100644 0.99.x/src/main/scala/gopher/channels/ChannelSupervisor.scala delete mode 100644 0.99.x/src/main/scala/gopher/channels/CloseableInput.scala delete mode 100644 0.99.x/src/main/scala/gopher/channels/Continuated.scala delete mode 100644 0.99.x/src/main/scala/gopher/channels/CurrentFlowTermination.scala delete mode 100644 0.99.x/src/main/scala/gopher/channels/DoneProvider.scala delete mode 100644 0.99.x/src/main/scala/gopher/channels/EffectedChannel.scala delete mode 100644 0.99.x/src/main/scala/gopher/channels/EffectedInput.scala delete mode 100644 0.99.x/src/main/scala/gopher/channels/EffectedOutput.scala delete mode 100644 0.99.x/src/main/scala/gopher/channels/ExpireChannel.scala delete mode 100644 0.99.x/src/main/scala/gopher/channels/FoldSelectorBuilder.scala delete mode 100644 0.99.x/src/main/scala/gopher/channels/ForeverSelectorBuilder.scala delete mode 100644 0.99.x/src/main/scala/gopher/channels/FutureInput.scala delete mode 100644 0.99.x/src/main/scala/gopher/channels/GopherAPIProvider.scala delete mode 100644 0.99.x/src/main/scala/gopher/channels/GrowingBufferedChannel.scala delete mode 100644 0.99.x/src/main/scala/gopher/channels/Input.scala delete mode 100644 0.99.x/src/main/scala/gopher/channels/InputOutput.scala delete mode 100644 0.99.x/src/main/scala/gopher/channels/InputSelectorBuilder.scala delete mode 100644 0.99.x/src/main/scala/gopher/channels/InputWithTimeouts.scala delete mode 100644 0.99.x/src/main/scala/gopher/channels/LazyChannel.scala delete mode 100644 0.99.x/src/main/scala/gopher/channels/OnceSelectorBuilder.scala delete mode 100644 0.99.x/src/main/scala/gopher/channels/OneTimeChannel.scala delete mode 100644 0.99.x/src/main/scala/gopher/channels/OrInput.scala delete mode 100644 0.99.x/src/main/scala/gopher/channels/Output.scala delete mode 100644 0.99.x/src/main/scala/gopher/channels/OutputWithTimeouts.scala delete mode 100644 0.99.x/src/main/scala/gopher/channels/PromiseFlowTermination.scala delete mode 100644 0.99.x/src/main/scala/gopher/channels/Selector.scala delete mode 100644 0.99.x/src/main/scala/gopher/channels/SelectorArguments.scala delete mode 100644 0.99.x/src/main/scala/gopher/channels/SelectorBuilder.scala delete mode 100644 0.99.x/src/main/scala/gopher/channels/SelectorFactory.scala delete mode 100644 0.99.x/src/main/scala/gopher/channels/UnbufferedChannelActor.scala delete mode 100644 0.99.x/src/main/scala/gopher/channels/ZippedInput.scala delete mode 100644 0.99.x/src/main/scala/gopher/channels/package.scala delete mode 100644 0.99.x/src/main/scala/gopher/goasync/AsyncApply.scala delete mode 100644 0.99.x/src/main/scala/gopher/goasync/AsyncIterable.scala delete mode 100644 0.99.x/src/main/scala/gopher/goasync/AsyncOption.scala delete mode 100644 0.99.x/src/main/scala/gopher/goasync/AsyncWrapper.scala delete mode 100644 0.99.x/src/main/scala/gopher/goasync/GoAsync.scala delete mode 100644 0.99.x/src/main/scala/gopher/package.scala delete mode 100644 0.99.x/src/main/scala/gopher/transputers/ReplicateTransputer.scala delete mode 100644 0.99.x/src/main/scala/gopher/transputers/TransputerSupervisor.scala delete mode 100644 0.99.x/src/main/scala/gopher/transputers/package.scala delete mode 100644 0.99.x/src/main/scala/gopher/util/ASTUtilImpl.scala delete mode 100644 0.99.x/src/main/scala/gopher/util/Effected.scala delete mode 100644 0.99.x/src/main/scala/gopher/util/IntIndexedReverse.scala delete mode 100644 0.99.x/src/main/scala/gopher/util/MacroUtil.scala delete mode 100644 0.99.x/src/main/scala/gopher/util/ReflectUtil.scala delete mode 100644 0.99.x/src/test/resources/application.conf delete mode 100644 0.99.x/src/test/scala/example/BetterSieveSuite.scala delete mode 100644 0.99.x/src/test/scala/example/Bingo.scala delete mode 100644 0.99.x/src/test/scala/example/BroadcasterSuite.scala delete mode 100644 0.99.x/src/test/scala/example/CopyFile.scala delete mode 100644 0.99.x/src/test/scala/example/FibonaccyAsyncSuite.scala delete mode 100644 0.99.x/src/test/scala/example/FibonaccyAsyncUnsugaredSuite.scala delete mode 100644 0.99.x/src/test/scala/example/FibonaccySuite.scala delete mode 100644 0.99.x/src/test/scala/gopher/channels/ChannelCleanupSuite.scala delete mode 100644 0.99.x/src/test/scala/gopher/channels/CommonTestObjects.scala delete mode 100644 0.99.x/src/test/scala/gopher/channels/SchedulerStartupTest.scala delete mode 100644 0.99.x/src/test/scala/gopher/hofasyn/FibbonacyAsyncLoopSuite.scala delete mode 100644 0.99.x/src/test/scala/gopher/hofasyn/HofAsyncSuite.scala delete mode 100644 0.99.x/src/test/scala/gopher/internal/FoldParseSuite.scala delete mode 100644 0.99.x/src/test/scala/gopher/obsolete/IOComposeSuite.scala delete mode 100644 0.99.x/src/test/scala/gopher/scope/DefersSuite.scala delete mode 100644 0.99.x/src/test/scala/gopher/scope/GoWithDeferSuite.scala delete mode 100644 0.99.x/src/test/scala/gopher/scope/ScopeMacroSuite.scala delete mode 100644 0.99.x/src/test/scala/gopher/scope/SimpleStatementSuite.scala delete mode 100644 0.99.x/src/test/scala/gopher/tags/Gen.scala delete mode 100644 0.99.x/src/test/scala/gopher/tags/Now.scala delete mode 100644 0.99.x/src/test/scala/gopher/time/TimeSuite.scala delete mode 100644 0.99.x/src/test/scala/gopher/transputers/ReplicateSuite.scala delete mode 100644 0.99.x/src/test/scala/gopher/transputers/TransputerRestartTest.scala diff --git a/0.99.x/README.md b/0.99.x/README.md deleted file mode 100644 index b47eb43f..00000000 --- a/0.99.x/README.md +++ /dev/null @@ -1,542 +0,0 @@ - -## Gopher: asynchronous implementation of go-like channels/selectors in scala -======= - -### Dependences: - - * scala 2.13.3 - * akka 2.6.8 - * scala-async - -#### Download: - -For scala 2.13: - - libraryDependencies += "com.github.rssh" %% "scala-gopher" % "0.99.15" - -(or `0.99.16-SNAPSHOT` for development version). - -For scala 2.12: - - libraryDependencies += "com.github.rssh" %% "scala-gopher" % "0.99.10" - - -Scala-gopher is open source (license is Apache2); binaries are available from the maven-central repository. - -## Overview - - Scala-gopher is a scala library, build on top of Akka and SIP-22 async, which provide an implementation of - CSP [Communicate Sequential Processes] primitives, known as 'Go-like channels.' Also, analogs of go/defer/recover control-flow constructions are provided. - -Note, which this is not an emulation of go language structures in Scala, but rather a reimplementation of the main ideas in 'scala-like' manner. - - - -### Initialization - - You need an instance of gopherApi for creating channels and selectors. The easiest way is to use one as Akka extension: - - import akka.actors._ - import gopher._ - - ...... - - val actorSystem = ActorSystem.create("system") - val gopherApi = Gopher(actorSystem) - - In akka.conf we can place config values in 'gopher' entry. - -## Control flow constructions: - -### goScope - - `goScope[T](body: =>T)` is expression, which allows to use inside `body` go-like 'defer' and 'recover' expression. - - Typical usage: -~~~ scala -import gopher._ -import java.io._ - -object CopyFile { - - def main(args: Array[String]): Unit = { - if (args.length != 3) { - System.err.println("usage: copy in out"); - } - copy(new File(args(1)), new File(args(2))) - } - - def copy(inf: File, outf: File): Long = - goScope { - val in = new FileInputStream(inf) - defer { - in.close() - } - val out = new FileOutputStream(outf); - defer { - out.close() - } - out.getChannel() transferFrom(in.getChannel(), 0, Long.MaxValue) - } - -} -~~~ - Here statements inside defer block executed at the end of goScope block in reverse order. - - Inside goScope we can use two pseudo functions: - -* `defer(body: =>Unit):Unit` - defer execution of `body` until the end of `go` or `goScope` block and previous defered blocks. -* `recover[T](f:PartialFunction[Throwable,T]):Boolean` -- can be used only within `defer` block with next semantics: -* * if exception was raised inside `go` or `goScope` than `recover` try to apply `f` to this exception and -* * * if `f` is applicable - set `f(e)` as return value of the block and return true -* * * otherwise - do nothing and return false -* * during normal exit - return false. - -You can look on `defer` as on stackable finally clauses, and on `defer` with `recover` inside as on `catch` clause. Small example: - -~~~ scala -val s = goScope{ - defer{ recover{ - case ex: Throwable => "CCC" - } } - throw new Exception("") - "QQQ" - } -~~~ - - will set `s` to "CCC". - - - -### go - - `go[T](body: =>T)(implicit ex:ExecutionContext):Future[T]` starts asynchronous execution of `body` in provided execution context. Inside go we can use `defer`/`recover` clauses and blocked read/write channel operations. - - Go implemented on top of [SIP-22](http://docs.scala-lang.org/sips/pending/async.html) async and share the same limitations. In addition to async/await transfoirm `go` provide lifting up asynchronous expressions inside some well-known hight-order functions (i.e. it is possible to use async operations inside for loops). Details are available in the tech report: https://arxiv.org/abs/1611.00602 . - - -## Channels - -Channels are used for asynchronous communication between execution flows. - -When using channel inside *go* block, you can look at one as on classic blocked queue with fixed size with methods read and write: - - - val channel = gopherApi.makeChannel[Int]; - - go { - channel.write(a) - } - ...... - go { - val i = channel.read - } - - - -* `channel.write(x)` - send x to channel and wait until one will be sent (it is possible us as synonyms `channel<~x` and `channel!x` if you prefer short syntax) -* `channel.read` or `(channel ?)` - blocking read - -Blocking operations can be used only inside `go` or `Async.await` blocks. - -Outside we can use asynchronous version: - -* `channel.awrite(x)` will write `x` and return to us `Future[Unit]` which will be executed after x will send -* `channel.aread` will return future to the value, which will be read. - -Also, channels can be closed. After this attempt to write will cause throwing 'ClosedChannelException.' Reading will be still possible up to 'last written value', after this attempt to read will cause the same exception. Also, each channel provides `done` input for firing close events. - -Note, closing channels are not mandatory; unreachable channels are garbage-collected regardless of they are closed or not. - - -Channels can be buffered and unbuffered. In a unbuffered channel, write return control to the caller after another side actually will start processing; buffered channel force provider to wait only if internal channel buffer is full. - -Also, you can use only `Input` or `Output` interfaces, where an appropriative read/write operations are defined. -For `Input`, exists usual collection functions, like `map`, `zip`, `takeN`, `fold` ... etc. Scala Iterable can be represented as `channels.Input` via method `gopherApi.iterableInput`. Also, we can use Scala futures as channels, which produce one value and then closes. For obtaining such input use `gopherApi.futureInput`. - -`|` (i.e. or) operator used for merged inputs, i.e. `(x|y).read` will read a value from channel x or y when one will be available. - -For each input and output you can create a facility with tracked timeout, i.e. if `in` is input, then -~~~ scala - val (inReady, inTimeouts) = in.withInputTimeouts(10 seconds) -~~~ -will return two inputs, where reading from `inReady` will return the same as reading from `in`. And if waiting for reading takes longer than 10 seconds then the value of timeout will be available in `inTimeouts`. Analogically we can create output with timeouts: -~~~ scala - val (outReady, outTimeouts) = out.withOutputTimeouts(10 seconds) -~~~ - - -Also, note that you can provide own Input and Output implementations by implementing callback `cbread` and `cbwrite` methods. - - -## Select loops and folds - - 'select statement' is somewhat similar to Unix 'select' syscall: - from a set of blocking operations select one who is ready to input/output and run it. - - The usual pattern of channel processing in go language is to wrap select operation into an endless loop. - - Gopher provides similar functionality: - -~~~ scala -go{ - for( s <- gopherApi.select.forever) - s match { - case i:channelA.read => ..do-something-with-i - case ch:channelB.read .. do-something-with-b - } -} -~~~ - - Here we read in the loop from channelA or channelB. - - Body of select loop must consist only of one `match` statement where - left parts in `case` clauses must have the following form - - * `v:channel.read` (for reading from channel) - * `v:Tye if (v==read(ch))` (for reading from channel or future) - * `v:channel.write if (v==expr)` (for writing `expr` into channel). - * `v:Type if (v==write(ch,expr))` (for writing `expr` into channel). - * `_` - for 'idle' action. - - - For endless loop inside `go` we can use the shortcut with the syntax of partial function: - -~~~ scala - gopherApi.select.forever{ - case i:channelA.read => ... do-something-with-i - case ch:channelB.read ... do-something-with-b - } -~~~ - - - Inside case actions, we can use blocking read/writes and await operations. Call of doExit in the implicit instance of `FlowTermination[T]` (for a forever loop this is `FlowTermination[Unit]`) can be used for exiting from the loop; `select.exit` and `select.shutdown` macroses are shortcuts for this. - - Example: - -~~~ scala -val channel = gopherApi.makeChannel[Int](100) - -val producer = channel.awrite(1 to 1000) - -@volatile var sum = 0; -val consumer = gopherApi.select.forever{ - case i: channerl.read => - sum = sum + i - if (i==1000) { - select.shutdown() - } -} - -Await.ready(consumer, 5.second) -~~~ - - A combination of variable and select loop better modeled with help 'fold over select' construction: - -~~~ scala -val sum = gopherApi.select.afold(0) { (state, selector) => - selector match { - case i: channel.read => - val nstate = state + i - if (i==1000) { - select.exit(nstate) - } - nstate - } -} -~~~ - - - More than one variables in state can be modeled with partial function case syntax: - -~~~ scala -val fib = select.afold((0,1)) { case ((x,y), s) => - s match { - case x:channel.write => (y,y+x) - case q:quit.read => select.exit((x,y)) - } -} -~~~ - - Also, we can use 'map over select' to represent results of handling of different events as input side of a channel: - -~~~ scala -val multiplexed = select amap { - case x:ch1.read => (s1,x) - case y:ch2.read => (s2,y) - } -~~~ - - - For using select operation not enclosed in a loop, scala-gopher provide - *select.once* syntax: - -~~~ scala -gopherApi.select.once{ - case i: channelA.read => s"Readed(${i})" - case x:channelB.write if (x==1) => s"Written(${x})" -} -~~~ - - - Such form can be called from any environment and will return `Future[String]`. Inside `go` you can wrap this in await of use 'for' syntax as with `forever`. - -~~~ scala -go { - ..... - val s = for(s <-gopherApi.select.once) - s match { - case i: channelA.read => s"Readed(${i})" - case x: channelB.write if (x==1) => s"Written(${x})" - } - -} -~~~ - - - and afold become fold: - -~~~ scala -go { - ... - val sum = select.fold(0) { (n,s) => - s match { - case x: channelA.read => n+x - case q: quit.read => select.exit(n) - } - } -} -~~~ - - amap - map - -~~~ scala -val multiplexed = for(s <- select) yield - s match { - case x:ch1.read => (s1,x) - case y:ch2.read => (s2,y) - } -~~~ - -## Done signals. - - Sometimes it is useful to receive a message when some `Input` becomes closed. Such inputs are named 'CloseableInputs' and provides a way to receive close notification in selector using `done` pseudo-type. - -~~~ scala - select.foreach{ - case x:ch.read => Console.println(s"received: ${x}") - case _:ch.done => Console.println(s"done") - select.exit(()) - } -~~~ - - Note, that you must exit from current flow in `done` handler, otherwise `done` signals will be intensively generated in a loop. - - -## Effected{Input,Output,Channel} - - One useful programming pattern, often used in CSP-style programming: have a channel from wich we read (or to where we write) as a part of a state. In Go language, this is usually modelled as a mutable variable, changed inside the same select statement, where one is read/written. - - In scala-gopher, we have the ability to use a technique of 'EffectedChannel', which can be seen as an entity, which holds channel, can be used in read/write and can be changed only via effect (operation, which accepts the previous state and returns the next). - -Let's look at the example: - -~~~ scala - def generate(n:Int, quit:Promise[Boolean]):Channel[Int] = - { - val channel = makeChannel[Int]() - channel.awriteAll(2 to n) andThen (_ => quit success true) - channel - } - - def filter(in:Channel[Int]):Input[Int] = - { - val filtered = makeChannel[Int]() - val sieve = makeEffectedInput(in) - sieve.aforeach { prime => - sieve <<= (_.filter(_ % prime != 0)) - filtered <~ prime - } - filtered - } -~~~ - -Here in 'filter', we generate a set of prime numbers, and make a sieve of Eratosthenes by sequentially applying 'filter' effect to state of sieve EffectedInput. - - -## Transputers - - The logic of data transformation between channels can be encapsulated in special `Transputer` concept. (Word 'transputer' was chosen - as a reminder about INMOS processor, for which one of the first CSP languages, Occam, was developed). You can view on transputer as - a representation of a restartable process that consists from: - - * Set of named input and output ports. - * Logic for propagating information from the input to the output ports. - * Possible state - * Logic of error recovering. - -I.e. we saw that Transputer is similar to Actor with the following difference: - When Actor provides reaction to incoming messages from the mailbox and sending signals to other actors, Transputers provide processing of incoming messages from input ports and sending outcoming messages to output ports. When operations inside Actor must not be blocked, operations inside Transputer can wait. - -Transformers are build hierarchically with the help of 3 operations: - - * select (logic is execution of a select statement ) - * parallel combination (logic is parallel execution of parts) - * replication (logic is a parallel execution of a set of identical transformers.) - -### Select transputer - - Let's look at a simple example: transputer with two input ports and one output. -When the same number has come from `inA` and `inB`, then -transputer prints `Bingo` on console and output this number to `out`: - -~~~ scala - trait BingoTransputer extends SelectTransputer - { - val inA = InPort[Int] - val inB = InPort[Int] - val out = OutPort[Boolean] - - loop { - case x:inA.read => - y = inB.read - out.write(x==y) - if (x==y) { - Console.println(s"Bingo: ${x}") - } - } - - } -~~~ - - A select loop is described in `loop` statement. - - To create transputer we can use `gopherApi.makeTransputer` call: -~~~ scala -val bing = gopherApi.makeTransputer[BingoTransputer] -~~~ - after the creation of transputer, we can create channels, connect one to ports and start transformer. - -~~~ scala -val inA = makeChannel[Int]() -bingo.inA.connect(inA) -val inB = makeChannel[Int]() -bingo.inB.connect(inB) -val out = makeChannel[Int]() -bingo.out.connect(out) - -val shutdownFuture = bingo.start() -~~~ - - - Then after we will write to `inA` and `inB` values `(1,1)` then `true` will become available for reading from `out`. - -#### Error recovery - - On an exception from a loop statement, transputer will be restarted with ports, connected to the same channels. Such behavior is the default; we can configure one by setting recovery policy: - -~~~ scala -val t = makeTransputer[MyType].recover { - case ex: MyException => SupervisorStrategy.Escalate - } -~~~ - - Recovery policy is a partial function from throwable to akka `SupervisorStrategy.Direction`. Escalated exceptions are passed to parent transputers or to TransputerSupervisor actor, which handle failures according to akka default supervisor strategy. - - How many times transputer can be restarted within given period can be configured via failureLimit call: - -~~~ scala - t.failureLimit(maxFailures = 20, windowDuration = 10 seconds) -~~~ - - This setting means that if 20 failures will occur during 10 seconds, then exception Transputer.TooManyFailures will be escalated to parent. - -### Par transputers. - - 'Par' is a group of transputers running in parallel. Par transputer can be created with the help of plus operator: - -~~~ scala -val par = (t1 + t1 + t3) -par.start() -~~~ - - When one from `t1`, `t2`, ... is stopped or failed, then all other members of `par` are stopped. After this `par` can be restarted according to current recovery policy. - - -### Replication - - Replicated transputer is a set of identical transputers t_{i}, running in parallel. It can be created with `gopherApi.replicate` call. Next code fragment: - -~~~ scala -val r = gopherApi.replicate[MyTransputer](10) -~~~ - - will produce ten copies of MyTransputer (`r` will be a container transputer for them). Ports of all replicated internal transputers will be shared with ports of the container. (I.e. if we will write something to input port then it will be read by one of the replicas; if one of the replicas will write something to out port, this will be visible in out port of the container.) - - Mapping from a container to replica port can be changed from sharing to other approaches, like duplicating or distributing, via applying port transformations. - - For example, next code fragment: - -~~~ scala -r.inA.duplicate() - .inB.distribute( _.hashCode ) -~~~ - - will set port `inA` be duplicated in replicas (i.e. message, send to container port `inA` will be received by each instance) and messages from `inB` will be distributed by hashcode: i.e. messages with the same hashcode will be directed to the same replica. Such behavior is useful when we keep in replicated transputer some state information about messages. - - Stopping and recovering of replicated transformer is the same as in `par` (i.e. stopping/failing of one instance will cause stopping/failing of container) - - Also note, that we can receive a sequence of replicated instances with the help of `ReplicateTransformer.replicated` method. - -## Unsugared interfaces - - It is worth to know that exists gopher API without macro-based syntax sugar. - -~~~ scala -( - new ForeverSelectorBuilder(gopherApi) - .reading(ch1){ x => something-x } - .writing(ch2,y){ y => something-y } - .idle(something idle).go -) -~~~ - - can be used instead of appropriative macro-based call. - - Moreover, for tricky things exists even low-level interface, which can combine computations by adding to functional interfaces, similar to continuations: - -~~~ scala -{ - val selector = new Selector[Unit](gopherApi) - selector.addReader(ch1, cont=>Some{ in => something-x - Future successful cont - } - ) - selector.addWriter(ch2, cont=>Some{(y,{something y; - Future successful cont - })}) - selector.addIdle(cont => {..do-something-when-idle; Future successful cont}) -} -~~~ - - Please, consult with source code for details. - - -## Additional Informatiom - ---------------------- - -* API reference: http://rssh.github.io/scala-gopher/api/index.html#package -* source code: https://github.com/rssh/scala-gopher -* presentations: - * Odessa Java/Scala Labs; Kiev Scala Meetup: Oct. 2014: http://www.slideshare.net/rssh1/scala-gopher2014 - * Wix R&D meetup. Mart 2016: http://www.slideshare.net/rssh1/csp-scala-wixmeetup2016 - * Scala Symposium. Oct. 2016. Amsterdam. http://www.slideshare.net/rssh1/scalagopher-cspstyle-programming-techniques-with-idiomatic-scala -* techreport: https://arxiv.org/abs/1611.00602 - - - Some related links: - -* [Communicating Sequential Processes book by Tony Hoare](http://www.usingcsp.com) -* [brief history of CSP in Bell-labs](http://swtch.com/~rsc/thread/) -* [introduction article about go defer/recover](http://blog.golang.org/defer-panic-and-recover) - diff --git a/0.99.x/build.sbt b/0.99.x/build.sbt deleted file mode 100644 index b03da841..00000000 --- a/0.99.x/build.sbt +++ /dev/null @@ -1,71 +0,0 @@ - -name:="scala-gopher" - -organization:="com.github.rssh" - -scalaVersion := "2.13.3" -//crossScalaVersions := Seq("2.12.7") - -resolvers += Resolver.sonatypeRepo("snapshots") - -resolvers += "Typesafe Repository" at "https://repo.typesafe.com/typesafe/releases/" - -scalacOptions ++= Seq("-unchecked","-deprecation", "-feature", "-Xasync", - /* , "-Ymacro-debug-lite" */ - /* , "-Ydebug" , "-Ylog:lambdalift" */ - ) - -libraryDependencies += scalaVersion( "org.scala-lang" % "scala-reflect" % _ ).value - -libraryDependencies += "org.scala-lang.modules" %% "scala-async" % "0.10.0" - -libraryDependencies += "org.scalatest" %% "scalatest" % "3.0.8" % "test" - -libraryDependencies += "com.typesafe.akka" %% "akka-actor" % "2.6.8" - -//testOptions in Test += Tests.Argument(TestFrameworks.ScalaTest, "-n", "Now") -fork in Test := true -//javaOptions in Test += s"""-javaagent:${System.getProperty("user.home")}/.ivy2/local/com.github.rssh/trackedfuture_2.11/0.3/jars/trackedfuture_2.11-assembly.jar""" - -version:="0.99.16-SNAPSHOT" - -credentials += Credentials(Path.userHome / ".sbt" / "sonatype_credentials") - -publishMavenStyle := true - -publishTo := version { (v: String) => - val nexus = "https://oss.sonatype.org/" - if (v.trim.endsWith("SNAPSHOT")) - Some("snapshots" at nexus + "content/repositories/snapshots") - else - Some("releases" at nexus + "service/local/staging/deploy/maven2") -} .value - - -publishArtifact in Test := false - -pomIncludeRepository := { _ => false } - -pomExtra := ( - http://rssh.github.com/scala-gopher - - - Apache 2 - http://www.apache.org/licenses/LICENSE-2.0 - repo - - - - git@github.com:rssh/scala-gopher.git - scm:git:git@github.com:rssh/scala-gopher.git - - - - rssh - Ruslan Shevchenko - rssh.github.com - - -) - - diff --git a/0.99.x/project/plugins.sbt b/0.99.x/project/plugins.sbt deleted file mode 100644 index 5de36b3f..00000000 --- a/0.99.x/project/plugins.sbt +++ /dev/null @@ -1,3 +0,0 @@ -//addSbtPlugin("com.typesafe.sbt" % "sbt-pgp" % "0.8.1") -addSbtPlugin("com.jsuereth" % "sbt-pgp" % "1.1.1") -addSbtPlugin("org.xerial.sbt" % "sbt-sonatype" % "2.5") diff --git a/0.99.x/src/main/scala/gopher/ChannelClosedException.scala b/0.99.x/src/main/scala/gopher/ChannelClosedException.scala deleted file mode 100644 index 62c5b770..00000000 --- a/0.99.x/src/main/scala/gopher/ChannelClosedException.scala +++ /dev/null @@ -1,10 +0,0 @@ -package gopher - -/** - * throwed when channel is closed: - *
    - *
  • during attempt to write to closed channel.
  • - *
  • during attempt to read from closed channel 'behind' last message.
  • - *
- **/ -class ChannelClosedException extends IllegalStateException("Channel is closed") diff --git a/0.99.x/src/main/scala/gopher/Defers.scala b/0.99.x/src/main/scala/gopher/Defers.scala deleted file mode 100644 index 3d51d12f..00000000 --- a/0.99.x/src/main/scala/gopher/Defers.scala +++ /dev/null @@ -1,163 +0,0 @@ -package gopher - -import scala.annotation.tailrec -import scala.util._ -import scala.util.control._ -import scala.reflect.runtime.universe.{Try => _, _} -import java.util.concurrent.atomic._ - -/** - * Construction Defers: defer/recover is alternative mechanism to exception handling, - * simular to one in Go language. - * - * We use one hidden in go and goScope construct are transformed to withDefers usage with - * help of macroses. - * - * It is also possible to use one unsugared (as in next example), but this is a bit verbose. - * - * - *
- *def parseCsv(fname: String): Either[String, Seq[Seq[Double]]] =
- *  withDefer[Either[String,Seq[Seq[Double]]]]{ d =>
- *    val in = Source.fromFile(fname)
- *    d.defer{ 
- *       var r = d.recover{
- *                 case FileNotFoundException => Left("fileNotFound")
- *               }
- *       if (!r) in.close() 
- *       d.recover {
- *         case ex: Throwable => Left(ex.getMessage)
- *       }
- *    }
- *    val retval:Either[String,Seq[Seq[Double]]] = Right{
- *        for( (line, nLine) <- in.getLines.toSeq zip Stream.from(1) ) yield withDefer[Seq[Double]] { d =>
- *           line.split(",") map { s=> 
- *                                 d.defer{
- *                                  d.recover{
- *                                     case ex: NumberFormatException =>
- *                                       throw new RuntimeException(s"parse error in line \${nLine} file \${fname} ")
- *                                  }
- *                                 }
- *                                 s.toDouble 
- *                               }
- *        }.toSeq
- *      }
- *    retval
- *}
- *
- **/ -class Defers[T] -{ - - /** - * can be used in main block - * (which can be plain or async) - * and store body for defered execution after - * evaluation of main block - **/ - def defer(body: =>Unit): Unit = - { - var prev = rl.get - var next = (()=>body)::prev - while(!rl.compareAndSet(prev,next)) { - prev = rl.get - next = (()=>body)::prev - } - } - - /** - * called after execution of main block, where - * all 'defered' blocks will be executed in one thread - * in LIFO order. - */ - def processResult(x: Try[T]):T = - { - tryProcess(x) match { - case Success(v) => v - case Failure(ex) => - throw ex - } - } - - def tryProcess(x: Try[T]):Try[T] = - { - last = x - unroll(rl getAndSet Nil) - last - } - - /** - * called inside defer blocks, where argument(t) - */ - def recover(f: PartialFunction[Throwable,T]): Boolean = { - var retval = false - for(e <- last.failed if (f.isDefinedAt(e) && !e.isInstanceOf[ControlThrowable])) { - last = Success(f(e)) - retval=true - } - retval - } - - @tailrec - private[this] def unroll(l: List[()=>Unit]):Try[T] = - l match { - case Nil => last - case head::tail => try { - head() - } catch { - case ex: Throwable => - last=Failure(ex) - } - // first component is for defer inside defer - unroll(rl.getAndSet(Nil) ++ tail) - } - - private[this] var last: Try[T] = Failure(Defers.NoResultException()) - - private[this] val rl: AtomicReference[List[()=>Unit]] = new AtomicReference(List()) -} - -object Defers -{ - - class NoResultException extends RuntimeException - - object NoResultException - { - def apply() = new NoResultException() - } - - /** - * same as scala.util.Try with one difference: - *ControlThrowable is catched and mapped to Failure. - */ - def controlTry[T](body: =>T):Try[T] = - { - try { - Success(body) - } catch { - case ex: ControlThrowable => Failure(ex) - case NonFatal(ex) => Failure(ex) - } - } - -} - -/** - * syntax sugar, for calling Defers. - */ -object withDefer -{ - - def apply[A](f: Defers[A] => A):A = - { val d = new Defers[A]() - d.processResult(Defers.controlTry(f(d))) - } - - def asTry[A](f: Defers[A] => A) = - { val d = new Defers[A]() - d.tryProcess(Defers.controlTry(f(d))) - } - -} - diff --git a/0.99.x/src/main/scala/gopher/FlowTermination.scala b/0.99.x/src/main/scala/gopher/FlowTermination.scala deleted file mode 100644 index d8874fab..00000000 --- a/0.99.x/src/main/scala/gopher/FlowTermination.scala +++ /dev/null @@ -1,51 +0,0 @@ -package gopher - -import gopher.channels.Continuated - -import scala.concurrent._ -import scala.annotation._ - -/** - * FlowTermination[-A] - termination of flow. - * - * Inside each block in select loop or - * select apply (once or forever) we have implicit - * FlowTermination entity, which we can use for - * exiting the loop. - * - *{{{ - * select.forever{ - * case x: info.read => Console.println(s"received from info \$x") - * case x: control.read => implicitly[FlowTermination[Unit]].doExit(()) - * } - *}}} - **/ -trait FlowTermination[-A] -{ - - /** - * terminate current flow with exception. - * Mostly used internally. - */ - def doThrow(e: Throwable): Unit - - /** - * terminate current flow and leave `a` as result of flow. - * have no effect if flow is already completed. - */ - def doExit(a:A): A@unchecked.uncheckedVariance - - /** - * check - if flow is completed. - */ - def isCompleted: Boolean - - - def throwIfNotCompleted(ex: Throwable): Unit - - - - - -} - diff --git a/0.99.x/src/main/scala/gopher/Gopher.scala b/0.99.x/src/main/scala/gopher/Gopher.scala deleted file mode 100644 index 49ef38e9..00000000 --- a/0.99.x/src/main/scala/gopher/Gopher.scala +++ /dev/null @@ -1,78 +0,0 @@ -package gopher - -import akka.actor._ -import com.typesafe.config._ -import scala.concurrent._ -import gopher.channels._ -import java.util.concurrent.Executors - -/** - * Akka extension which provide gopherApi interface - *@see GopherAPI - **/ -class GopherImpl(system: ExtendedActorSystem) - extends GopherAPI(system, - GopherAPIExtensionHelper.retrieveConfiguredExecutor(system)) - with Extension -{ - -} - -/** - * Factory object for Akka extension - * - *{{{ - * val actorSystem = ActorSystem("myapp") - * val gopherApi = Gopher(actorSystem) - *}}} - **/ -object Gopher extends ExtensionId[GopherImpl] - with ExtensionIdProvider -{ - override def lookup = Gopher - - override def createExtension(system: ExtendedActorSystem) - = new GopherImpl(system) - - override def get(system: ActorSystem): GopherImpl = super.get(system) - -} - -object GopherAPIExtensionHelper -{ - - def retrieveConfiguredExecutor(system: ExtendedActorSystem): ExecutionContext = { - val config = system.settings.config.atKey("gopher") - if (config.hasPath("threadPool")) { - var sType = "fixed" - try { - sType = config.getString("threadPool.size") - } catch { - case ex: ConfigException.Missing => - system.log.warning("gopher initialization, threadPool.type is missign, use default" +sType) - } - sType match { - case "fixed" => - var size = 4; - try { - size = config.getInt("threadPool.size") - } catch { - case ex: ConfigException.Missing => - system.log.warning("gopher initialization, threadPool.size is missing, use default: "+size) - } - ExecutionContext.fromExecutorService( Executors.newFixedThreadPool(size) ) - case "cached" => - ExecutionContext.fromExecutorService( Executors.newCachedThreadPool() ) - case "single" => - ExecutionContext.fromExecutorService( Executors.newSingleThreadExecutor() ) - case "global" => ExecutionContext.global - case _ => throw new IllegalStateException("""Invalid threadPool.type in config, must be one of "fixed", "cached", "single", "global" """) - } - } else { - // use defautl execution context. - ExecutionContext.global - } - } - - -} diff --git a/0.99.x/src/main/scala/gopher/GopherAPI.scala b/0.99.x/src/main/scala/gopher/GopherAPI.scala deleted file mode 100644 index 3126d83e..00000000 --- a/0.99.x/src/main/scala/gopher/GopherAPI.scala +++ /dev/null @@ -1,215 +0,0 @@ -package gopher - -import java.util.concurrent.atomic.AtomicLong - -import akka.actor._ -import com.typesafe.config._ -import gopher.channels._ -import gopher.transputers._ - -import scala.concurrent.duration._ -import scala.concurrent.{Channel => _, _} -import scala.language.experimental.macros -import scala.language.postfixOps -import scala.reflect.macros.blackbox.Context -import scala.util._ - -/** - * Api for providing access to channel and selector interfaces. - */ -class GopherAPI(as: ActorSystem, es: ExecutionContext) -{ - - /** - * obtain select factory - * - * {{{ - * goopherApi.select.once[String] { - * case x: a.read => s"\${x} from A" - * case x: b.read => s"\${x} from B" - * case _ => "IDLE" - * } - * }}} - */ - val select: SelectFactory = - new SelectFactory(this) - - /** - * Generic schema for making objects, which requiere gopherAPI for constructions. - * - **/ - def make[T](args: Any*): T = macro GopherAPI.makeImpl[T] - - /** - * obtain channel - * - *{{{ - * val channel = gopherApi.makeChannel[Int]() - * channel.awrite(1 to 100) - *}}} - */ - @inline - def makeChannel[A](capacity: Int = 0): Channel[A] = - Channel[A](capacity)(this) - - /* - def makeEffectedInput[A](in: Input[A], threadingPolicy: ThreadingPolicy = ThreadingPolicy.Single): EffectedInput[A] = - EffectedInput(in,threadingPolicy) - - def makeEffectedOutput[A](out: Output[A], threadingPolicy: ThreadingPolicy = ThreadingPolicy.Single) = - EffectedOutput(out,threadingPolicy) - - def makeEffectedChannel[A](ch: Channel[A], threadingPolicy: ThreadingPolicy = ThreadingPolicy.Single) = - EffectedChannel(ch,threadingPolicy) - */ - - /** - * Represent Scala future as channel from which we can read one value. - *@see gopher.channels.FutureInput - */ - def futureInput[A](future:Future[A]): FutureInput[A] = new FutureInput(future, this) - - /** - * Represent Scala iterable as channel, where all values can be readed in order of iteration. - */ - def iterableInput[A](iterable:Iterable[A]): Input[A] = Input.asInput(iterable, this) - - - /** - * create and start instance of transputer with given recovery policy. - *@see gopher.Transputer - */ - def makeTransputer[T <: Transputer](recoveryPolicy:PartialFunction[Throwable,SupervisorStrategy.Directive]): T = macro GopherAPI.makeTransputerImpl2[T] - - def makeTransputer[T <: Transputer]: T = macro GopherAPI.makeTransputerImpl[T] - - /** - * create transputer which contains n instances of X - * where ports are connected to the appropriate ports of each instance in paraller. - * {{{ - * val persistStep = replicate[PersistTransputer](nDBConnections) - * }}} - */ - def replicate[T<: Transputer](n:Int): Transputer = macro Replicate.replicateImpl[T] - - /** - * actor system which was passed during creation - **/ - def actorSystem: ActorSystem = as - - /** - * execution context used for managing calculation steps in channels engine. - **/ - def gopherExecutionContext: ExecutionContext = es - - /** - * the configuration of the gopher system. By default is contained under 'gopher' key in top-level config. - **/ - def config: Config = as.settings.config.atKey("gopher") - - /** - * time API - */ - lazy val time = new Time(this,gopherExecutionContext) - - - lazy val idleTimeout: FiniteDuration = { - val m = try { - config.getInt("idle-detection-tick") - } catch { - case ex: ConfigException.Missing => 100 - } - m.milliseconds - } - - - lazy val defaultExpireCapacity: Int = { - try { - config.getInt("default-expire-capacity") - } catch { - case ex: ConfigException.Missing => 1000 - } - } - - def currentFlow = CurrentFlowTermination - - - //private[gopher] val idleDetector = new IdleDetector(this) - - private[gopher] val continuatedProcessorRef: ActorRef = { - val props = Props(classOf[ChannelProcessor], this) - actorSystem.actorOf(props,name="channelProcessor") - } - - private[gopher] val channelSupervisorRef: ActorRef = { - val props = Props(classOf[ChannelSupervisor], this) - actorSystem.actorOf(props,name="channels") - } - - private[gopher] val transputerSupervisorRef: ActorRef = { - val props = Props(classOf[TransputerSupervisor], this) - actorSystem.actorOf(props,name="transputerSupervisor") - } - - private[gopher] def newChannelId: Long = - channelIdCounter.getAndIncrement - - private[gopher] def continue[A](next:Future[Continuated[A]], ft:FlowTermination[A]): Unit = - next.onComplete{ - case Success(cont) => - continuatedProcessorRef ! cont - case Failure(ex) => ft.throwIfNotCompleted(ex) - }(gopherExecutionContext) - - private[this] val channelIdCounter = new AtomicLong(0L) - - -} - -object GopherAPI -{ - - def makeImpl[T : c.WeakTypeTag](c:Context)(args: c.Expr[Any]*): c.Expr[T] = { - import c.universe._ - val wt = weakTypeOf[T] - if (wt.companion =:= NoType) { - c.abort(c.prefix.tree.pos,s"type ${wt.typeSymbol} have no companion") - } - val sym = wt.typeSymbol.companion - val r = q"${sym}.apply[..${wt.typeArgs}](..${args})(${c.prefix})" - c.Expr[T](r) - } - - def makeTransputerImpl[T <: Transputer : c.WeakTypeTag](c:Context):c.Expr[T] = { - import c.universe._ - c.Expr[T](q"${c.prefix}.makeTransputer[${weakTypeOf[T]}](gopher.Transputer.RecoveryPolicy.AlwaysRestart)") - } - - def makeTransputerImpl2[T <: Transputer : c.WeakTypeTag](c:Context)(recoveryPolicy:c.Expr[PartialFunction[Throwable,SupervisorStrategy.Directive]]):c.Expr[T] = { - import c.universe._ - //---------------------------------------------- - // generate incorrect code: see https://issues.scala-lang.org/browse/SI-8953 - //c.Expr[T](q"""{ def factory():${c.weakTypeOf[T]} = new ${c.weakTypeOf[T]} { - // def api = ${c.prefix} - // def recoverFactory = factory - // } - // val retval = factory() - // retval - // } - // """) - //---------------------------------------------- - // so, let's create subclass - val implName = c.freshName(c.symbolOf[T].name) - c.Expr[T](q"""{ - class ${implName} extends ${c.weakTypeOf[T]} { - def api = ${c.prefix} - def recoverFactory = () => new ${implName} - } - val retval = new ${implName} - retval.recoverAppend(${recoveryPolicy}) - retval - } - """) - } - -} diff --git a/0.99.x/src/main/scala/gopher/ThreadingPolicy.scala b/0.99.x/src/main/scala/gopher/ThreadingPolicy.scala deleted file mode 100644 index 8c30413a..00000000 --- a/0.99.x/src/main/scala/gopher/ThreadingPolicy.scala +++ /dev/null @@ -1,10 +0,0 @@ -package gopher - -sealed trait ThreadingPolicy - -object ThreadingPolicy -{ - case object Single extends ThreadingPolicy - case object Multi extends ThreadingPolicy -} - diff --git a/0.99.x/src/main/scala/gopher/Time.scala b/0.99.x/src/main/scala/gopher/Time.scala deleted file mode 100644 index 488389c9..00000000 --- a/0.99.x/src/main/scala/gopher/Time.scala +++ /dev/null @@ -1,91 +0,0 @@ -package gopher - -import java.time.Instant - -import gopher.channels.{Channel, ExpireChannel, Input, OneTimeChannel} - -import scala.concurrent.duration.FiniteDuration -import scala.concurrent.{ExecutionContext, Future, Promise} -import scala.language.experimental.macros -import scala.reflect.macros.blackbox.Context -import scala.util.Failure - -/** - * Time API, simular to one in golang standard library. - * @see gopherApi#time - */ -class Time(gopherAPI: GopherAPI, ec:ExecutionContext) { - - - def after(duration: FiniteDuration): Input[Instant] = - { - val ch = OneTimeChannel.apply[Instant]()(gopherAPI) - gopherAPI.actorSystem.scheduler.scheduleOnce(duration){ - ch.awrite(Instant.now()) - }(ec) - ch - } - - def asleep(duration: FiniteDuration): Future[Instant] = - { - val p = Promise[Instant]() - gopherAPI.actorSystem.scheduler.scheduleOnce(duration){ - p success Instant.now() - }(ec) - p.future - } - - def sleep(duration: FiniteDuration): Instant = macro Time.sleepImpl - - /** - * create ticker. When somebody read this ticker, than one receive LocalDateTime - * messages. When nobody reading - messages are expired. - * @param duration - * @return - */ - def tick(duration: FiniteDuration): Input[Instant] = - { - newTicker(duration) - } - - class Ticker(duration: FiniteDuration) - { - val ch = ExpireChannel[Instant](duration,0)(gopherAPI) - val cancellable = gopherAPI.actorSystem.scheduler.schedule(duration,duration)(tick)(ec) - gopherAPI.actorSystem.registerOnTermination{ - if (!cancellable.isCancelled) cancellable.cancel() - } - - def tick():Unit = { - if (!cancellable.isCancelled) { - ch.awrite(Instant.now()).onComplete{ - case Failure(ex:ChannelClosedException) => cancellable.cancel() - case Failure(ex) => cancellable.cancel() // strange, but stop. - case _ => - }(ec) - } - } - - - } - - def newTicker(duration: FiniteDuration): Channel[Instant] = - { - (new Ticker(duration)).ch - } - - def now() = Instant.now() - -} - -object Time -{ - - def sleepImpl(c:Context)(duration:c.Expr[FiniteDuration]):c.Expr[Instant] = - { - import c.universe._ - val r = q"scala.async.Await.await(${c.prefix}.asleep(${duration}))(${c.prefix}.executionContext)" - c.Expr[Instant](r) - } - -} \ No newline at end of file diff --git a/0.99.x/src/main/scala/gopher/Transputer.scala b/0.99.x/src/main/scala/gopher/Transputer.scala deleted file mode 100644 index 430fc904..00000000 --- a/0.99.x/src/main/scala/gopher/Transputer.scala +++ /dev/null @@ -1,448 +0,0 @@ -package gopher - -import scala.language.experimental.macros -import gopher.channels._ -import gopher.util._ -import transputers._ -import scala.concurrent._ -import scala.concurrent.duration._ -import akka.actor._ - - -/** - * Reusable unit of application structure, which consists from - * set of input ports, set of output ports and behaviour - * - * Transputers can be created as elementary behaviour, descibed by select - * statement and then can be combined into larger structures - * - * Transputers can be recovered from execeptions (i.e. transputer can be restarted or resume execution) - * or escalated to parent transputers or root superviser. - * - */ -trait Transputer -{ - - class InPort[A](input:Input[A]) extends Input[A] - { - - override def cbread[B](f: ContRead[A,B] => Option[ContRead.In[A] => Future[Continuated[B]]],ft: FlowTermination[B]): Unit = - v.cbread(f,ft) - - def api: gopher.GopherAPI = Transputer.this.api - - def connect(x: Input[A]): Unit = - { v=x } - - def connect(outPort: Transputer#OutPort[A], bufferSize:Int = 1): Unit = - { - val ch = api.makeChannel[A](bufferSize) - v = ch - outPort.v = ch - } - - def <~~<(x: Transputer#OutPort[A]) = connect(x) - - // get other side of port input if this is possible. - def outputSide : Option[Output[A]] = - v match { - case out: Output[A] => Some(out) - case _ => None - } - - def *! : Output[A] = outputSide.getOrElse( - throw new IllegalStateException("Can't get output side of port input") - ) - - var v: Input[A] = input - - } - - object InPort - { - @inline def apply[A]():InPort[A] = new InPort(new LazyChannel[A](api)) - } - - class OutPort[A](output:Output[A]) extends Output[A] - { - override def cbwrite[B](f: ContWrite[A,B] => Option[(A, Future[Continuated[B]])], ft: FlowTermination[B]): Unit = - { - v.cbwrite(f, ft) - } - - def api: gopher.GopherAPI = Transputer.this.api - - def connect(x: Output[A]): Unit = - { v=x } - - def connect(inPort: Transputer#InPort[A], bufferSize:Int = 1): Unit = - { - val ch = api.makeChannel[A](bufferSize) - v = ch - inPort.connect(ch) - } - - def >~~> (x: Transputer#InPort[A]) = connect(x) - - def inputSide: Option[Input[A]] = - v match { - case in: Input[A] => Some(in) - case _ => None - } - - def *! :Input[A] = inputSide.getOrElse( - throw new IllegalStateException("Can't get input side of port output "+v) - ) - - var v: Output[A] = output - } - - object OutPort - { - @inline def apply[A]():OutPort[A] = new OutPort(new LazyChannel[A](api)) - } - - def +(p: Transputer) = new ParTransputer(api, Seq(this,p)) - - def start():Future[Unit] = { - onStart() - api.transputerSupervisorRef ! TransputerSupervisor.Start(this) - flowTermination.future - } - - def goOnce: Future[Unit] - - def stop(): Unit - - /** - * set recover function - **/ - def recover(f: PartialFunction[Throwable,SupervisorStrategy.Directive]): this.type = { - recoveryFunction = f - this - } - - /** - * append recover function to existing - **/ - def recoverAppend(f: PartialFunction[Throwable,SupervisorStrategy.Directive]): this.type = - { recoveryFunction = recoveryFunction orElse f - this - } - - /** - * set failure limit. - * (when number of failures during windowsDuration is bigger than maxFailures, - * TooManyFailures exception is escalated to parent transputer. - **/ - def failureLimit(maxFailures:Int = recoveryLimits.maxFailures, - windowDuration: Duration = recoveryLimits.windowDuration): this.type = - { - recoveryLimits = Transputer.RecoveryLimits(maxFailures, windowDuration) - this - } - - def api: GopherAPI - - // internal API. - - /** - * copyState from previous instance when transputer is restarted. - * can be overriden in subclasses (by default: do nothing) - * - * Note, that port connection is restored before call of copyState - */ - def copyState(prev: Transputer): Unit = {} - - /** - * copy conection from previous instance when transputer is - * restarted. - **/ - def copyPorts(prev: Transputer): Unit = - { - import scala.reflect._ - import scala.reflect.runtime.{universe=>ru} - val mirror = ru.runtimeMirror(this.getClass.getClassLoader) - - def copyVar[T:ClassTag:ru.TypeTag,V:ClassTag](x:T, y: T, varName: String): Unit = - { - val imx = mirror.reflect(x) - val imy = mirror.reflect(y) - val field = ru.typeOf[T].decl(ru.TermName(varName)).asTerm.accessed.asTerm - - val v = imy.reflectField(field).get - imx.reflectField(field).set(v) - - } - - def copyPorts[T:ru.TypeTag:ClassTag]:Unit = - { - val List(newIns, prevIns) = List(this, prev) map (ReflectUtil.retrieveVals[T,Transputer](ru)(mirror,_)) - for((x,y) <- newIns zip prevIns) copyVar(x,y,"v") - } - - copyPorts[InPort[_]] - copyPorts[OutPort[_]] - } - - - /** - * Used for recover failed instances - */ - def recoverFactory: ()=>Transputer - - /** - * called when transducer is started. - */ - protected def onStart(): Unit = { } - - /** - * called when transducer is restarted. - * - *@param prev - previous (i.e. failed) instance of trnasputer. - */ - protected def onRestart(prev:Transputer): Unit = { } - - - /** - * called when transducer is choose to resume durign recovery. - */ - protected def onResume(): Unit = { } - - /** - * called when failure is escalated. - **/ - protected def onEscalatedFailure(ex: Throwable): Unit = { } - - /** - * called when transputer is stopped. - */ - protected def onStop(): Unit = { } - - private[gopher] def beforeResume(): Unit = - { - flowTermination = createFlowTermination() - onResume(); - } - - private[gopher] def beforeRestart(prev: Transputer): Unit = - { - if (!(prev eq null)) { - recoveryStatistics = prev.recoveryStatistics - recoveryLimits = prev.recoveryLimits - recoveryFunction = prev.recoveryFunction - parent = prev.parent - } - onRestart(prev) - } - - private[gopher] var recoveryStatistics = Transputer.RecoveryStatistics( ) - private[gopher] var recoveryLimits = Transputer.RecoveryLimits( ) - private[gopher] var recoveryFunction: PartialFunction[Throwable, SupervisorStrategy.Directive] = PartialFunction.empty - private[gopher] var parent: Option[Transputer] = None - private[gopher] var flowTermination: PromiseFlowTermination[Unit] = createFlowTermination() - private[gopher] var replicaNumber = 1 - - private[this] def createFlowTermination() = new PromiseFlowTermination[Unit]() { - - override def doThrow(e:Throwable): Unit = - { - onEscalatedFailure(e) - super.doThrow(e) - } - - override def doExit(a:Unit): Unit = - { - super.doExit(()) - onStop() - } - - - } - - /** - * return replica number of current instance, if - * transponder run replicated. - **/ - protected def replica = replicaNumber - - import akka.event.LogSource - implicit def logSource: LogSource[Transputer] = new LogSource[Transputer] { - def genString(t: Transputer) = t.getClass.getName+"/"+t.replica - } - -} - -/** - * mix this trait to ypu transputer for access to akka logging. - **/ -trait TransputerLogging -{ - this: Transputer => - - val log = akka.event.Logging(api.actorSystem, this) -} - -object Transputer -{ - - - - case class RecoveryStatistics( - var nFailures: Int = 0, - var windowStart: Long = 0, - var firstFailure: Option[Throwable] = None, - var lastFailure: Option[Throwable] = None - ) { - - def failure(ex: Throwable, recoveryLimits: RecoveryLimits, nanoNow: Long): Boolean = - { - val same = sameWindow(recoveryLimits, nanoNow) - nFailures +=1 - if (firstFailure.isEmpty) { - firstFailure = Some(ex) - } - lastFailure = Some(ex) - return (same && nFailures >= recoveryLimits.maxFailures) - } - - - def sameWindow(recoveryLimits: RecoveryLimits, nanoNow: Long): Boolean = - { - if ((nanoNow - windowStart) > recoveryLimits.windowDuration.toNanos) { - nFailures = 0 - windowStart = nanoNow - firstFailure = None - lastFailure = None - false - } else { - true - } - } - - } - - - case class RecoveryLimits( - var maxFailures: Int = 10, - var windowDuration: Duration = 1 second - ) - - class TooManyFailures(t: Transputer) extends RuntimeException(s"Too many failures for ${t}", t.recoveryStatistics.firstFailure.get) - { - addSuppressed(t.recoveryStatistics.lastFailure.get) - } - - object RecoveryPolicy { - import scala.util.control._ - - val AlwaysRestart: PartialFunction[Throwable,SupervisorStrategy.Directive] = - { case x: TooManyFailures => SupervisorStrategy.Escalate - case NonFatal(ex) => SupervisorStrategy.Restart - } - - val AlwaysEscalate: PartialFunction[Throwable,SupervisorStrategy.Directive] = - { case ex => SupervisorStrategy.Escalate } - - } - - - - -} - -/** - * Transputer, where dehaviour can be described by selector function - * - **/ -trait SelectTransputer extends ForeverSelectorBuilder with Transputer -{ - - /** - * configure loop in selector - */ - def loop(f: PartialFunction[Any,Unit]): Unit = macro SelectorBuilderImpl.loop[Unit] - - - def stop():Unit = stopFlowTermination() - - /** - * When called inside loop - stop execution of selector, from outside - terminate transformer - */ - private[this] def stopFlowTermination(implicit ft:FlowTermination[Unit] = flowTermination): Unit = - ft.doExit(()) - - protected override def onEscalatedFailure(ex: Throwable): Unit = - { - super.onEscalatedFailure(ex) - selector.throwIfNotCompleted(ex) - } - - protected override def onStop(): Unit = - { - super.onStop() - if (!selector.isCompleted) { - selector.doExit(()) - } - } - - def goOnce: Future[Unit] = selectorRun - - private[gopher] override def beforeResume() : Unit = - { - super.beforeResume() - //selector.clear() - selectorInit() - } - - protected var selectorInit: ()=>Unit = - { () => throw new IllegalStateException("selectorInit us not initialized yet") } - -} - -class ParTransputer(override val api: GopherAPI, var childs:Seq[Transputer]) extends Transputer -{ - - childs.foreach(_.parent = Some(this)) - - def goOnce: Future[Unit] = { - implicit val ec: ExecutionContext = api.gopherExecutionContext - @volatile var inStop = false - def withStopChilds[A](f: Future[A]):Future[A] = - { - f.onComplete{ _ => - if (!inStop) { - inStop = true - stopChilds() - } - } - f - } - withStopChilds( - Future.sequence(childs map( x=> withStopChilds(x.start()) ) ) - ) map (_ => ()) - } - - def stop(): Unit = - { - stopChilds() - } - - override def +(p: Transputer) = new ParTransputer(api, childs :+ p) - - private[this] def stopChilds(): Unit = - for(x <- childs if (!x.flowTermination.isCompleted) ) { - x.flowTermination.doExit(()) - } - - - def recoverFactory: () => Transputer = () => new ParTransputer(api,childs) - - private[gopher] override def beforeResume() : Unit = - { - super.beforeResume() - for(ch <- childs) ch.beforeResume() - } - -} - diff --git a/0.99.x/src/main/scala/gopher/channels/ActorBackedChannel.scala b/0.99.x/src/main/scala/gopher/channels/ActorBackedChannel.scala deleted file mode 100644 index 5588bfe3..00000000 --- a/0.99.x/src/main/scala/gopher/channels/ActorBackedChannel.scala +++ /dev/null @@ -1,111 +0,0 @@ -package gopher.channels - - -import akka.actor._ -import akka.pattern._ -import gopher._ -import gopher.channels.ContRead.In - -import scala.concurrent.{Channel=>_,_} -import scala.concurrent.duration._ -import scala.language.experimental.macros -import scala.language.postfixOps -import scala.util._ - -class ActorBackedChannel[A](futureChannelRef: Future[ActorRef], override val api: GopherAPI) extends Channel[A] -{ - - thisActorBackedChannel => - - def cbread[B](f: ContRead[A,B] => Option[ContRead.In[A] => Future[Continuated[B]]], flwt: FlowTermination[B] ): Unit = - { - val cont = ContRead(f,this, flwt) - def applyClosed() = - { - f(cont) foreach { f1 => try { - api.continue( f1(ContRead.ChannelClosed), flwt) - } catch { - case ex: Throwable => flwt.doThrow(ex) - } - } - } - implicit val ec = api.gopherExecutionContext - if (closed) { - if (closedEmpty) { - applyClosed() - } else { - // TODO: ask timeput on closed channel set in config. - futureChannelRef.foreach{ ref => val f = ref.ask(ClosedChannelRead(cont))(5 seconds) - f.onComplete{ - case Failure(e) => - if (e.isInstanceOf[AskTimeoutException]) { - applyClosed() - } - case Success(ChannelCloseProcessed(0)) => - closedEmpty = true - case _ => // do nothing - } - } - } - } else { - futureChannelRef.foreach( _ ! cont ) - } - } - - private def contRead[B](x:ContRead[A,B]): Unit = - futureChannelRef.foreach( _ ! x )(api.gopherExecutionContext) - - def cbwrite[B](f: ContWrite[A,B] => Option[(A,Future[Continuated[B]])], flwt: FlowTermination[B] ): Unit = { - val cont = ContWrite(f, this, flwt) - if (closed) { - flwt.doThrow(new ChannelClosedException()) - } else { - futureChannelRef.foreach(_ ! cont)(api.gopherExecutionContext) - } - } - - private def contWrite[B](x:ContWrite[A,B]): Unit = - futureChannelRef.foreach( _ ! x )(api.gopherExecutionContext) - - //private[this] implicit val ec = api.executionContext - - def isClosed: Boolean = closed - - def close(): Unit = - { - futureChannelRef.foreach( _ ! ChannelClose )(api.gopherExecutionContext) - closed=true - } - - val done = new Input[Unit] { - - /** - * apply f, when input will be ready and send result to API processor - */ - override def cbread[B](f: (ContRead[Unit, B]) => Option[(In[Unit]) => Future[Continuated[B]]], ft: FlowTermination[B]): Unit = - { - val cr = ContRead(f,this,ft) - if (isClosed) { - applyDone(cr) - } else { - futureChannelRef.foreach( _ ! ChannelCloseCallback(cr) )(api.gopherExecutionContext) - } - } - - /** - * instance of gopher API - */ - override def api: GopherAPI = thisActorBackedChannel.api - } - - - override protected def finalize(): Unit = - { - // allow channel actor be grabage collected - futureChannelRef.foreach( _ ! ChannelRefDecrement )(api.gopherExecutionContext) - } - - private var closed = false - private var closedEmpty = false -} - diff --git a/0.99.x/src/main/scala/gopher/channels/BaseBufferedChannelActor.scala b/0.99.x/src/main/scala/gopher/channels/BaseBufferedChannelActor.scala deleted file mode 100644 index fc04a179..00000000 --- a/0.99.x/src/main/scala/gopher/channels/BaseBufferedChannelActor.scala +++ /dev/null @@ -1,53 +0,0 @@ -package gopher.channels - -import akka.actor._ -import scala.language._ -import scala.concurrent._ -import scala.collection.immutable._ -import gopher._ - - -/** - * ChannelActor - actor, which leave - */ -abstract class BaseBufferedChannelActor[A](id:Long, api: GopherAPI) extends ChannelActor[A](id,api) -{ - - def processReaders() : Boolean = - { - var retval = false - while(!readers.isEmpty && nElements > 0) { - val current = readers.head - readers = readers.tail - retval ||= processReader(current) - } - retval - } - - def stopIfEmpty: Boolean = - { - require(closed==true) - if (nElements == 0) { - stopReaders() - } - stopWriters() - if (nElements == 0) { - doClose() - if (nRefs == 0) { - // here we leave 'closed' channels in actor-system untile they will be - // garbage-collected. TODO: think about actual stop ? - self ! GracefullChannelStop - } - true - } else - false - } - - protected[this] def processReader[B](reader:ContRead[A,B]): Boolean - - - protected[this] def getNElements(): Int = nElements - - protected[this] var nElements=0 - -} diff --git a/0.99.x/src/main/scala/gopher/channels/BufferedChannelActor.scala b/0.99.x/src/main/scala/gopher/channels/BufferedChannelActor.scala deleted file mode 100644 index 227aa951..00000000 --- a/0.99.x/src/main/scala/gopher/channels/BufferedChannelActor.scala +++ /dev/null @@ -1,111 +0,0 @@ -package gopher.channels - -import akka.actor._ -import scala.language._ -import scala.concurrent._ -import scala.collection.immutable._ -import gopher._ - - -/** - * ChannelActor - actor, which leave - */ -class BufferedChannelActor[A](id:Long, capacity:Int, api: GopherAPI) extends BaseBufferedChannelActor[A](id,api) -{ - - - protected[this] def onContWrite(cwa: gopher.channels.ContWrite[A, _]): Unit = - { - if (closed) { - cwa.flowTermination.throwIfNotCompleted(new ChannelClosedException()) - } else { - if (nElements==capacity) { - writers = writers :+ cwa - } else { - val prevNElements = nElements - if (processWriter(cwa) && prevNElements==0) { - processReaders() - } - } - } - } - - protected[this] def onContRead(cra: gopher.channels.ContRead[A, _]): Unit = - { - if (nElements==0) { - if (closed) { - processReaderClosed(cra) - } else { - readers = readers :+ cra - } - } else { - val prevNElements = nElements - if (processReader(cra)) { - if (closed) { - stopIfEmpty - } else if (prevNElements==capacity) { - checkWriters - } - } - } - } - - - protected[this] def processReader[B](reader:ContRead[A,B]): Boolean = - reader.function(reader) match { - case Some(f1) => - val readedElement = elementAt(readIndex) - nElements-=1 - readIndex+=1 - readIndex%=capacity - Future{ - val cont = f1(ContRead.In value readedElement ) - api.continue(cont, reader.flowTermination) - }(api.gopherExecutionContext) - true - case None => - false - } - - - def checkWriters: Boolean = - { - var retval = false - while(!writers.isEmpty && nElements < capacity) { - val current = writers.head - writers = writers.tail - val processed = processWriter(current) - retval ||= processed - } - retval - } - - private[this] def processWriter[B](writer:ContWrite[A,B]): Boolean = - writer.function(writer) match { - case Some((a,cont)) => - nElements+=1 - setElementAt(writeIndex,a) - writeIndex+=1 - writeIndex%=capacity - api.continue(cont, writer.flowTermination) - true - case None => - false - } - - - @inline - private[this] def elementAt(i:Int): A = - buffer(i).asInstanceOf[A] - - @inline - private[this] def setElementAt(i:Int, a:A): Unit = - buffer(i) = a.asInstanceOf[AnyRef] - - - // boxed representation of type. - val buffer= new Array[AnyRef](capacity+1) - var readIndex=0 - var writeIndex=0 - -} diff --git a/0.99.x/src/main/scala/gopher/channels/Channel.scala b/0.99.x/src/main/scala/gopher/channels/Channel.scala deleted file mode 100644 index 56f588c6..00000000 --- a/0.99.x/src/main/scala/gopher/channels/Channel.scala +++ /dev/null @@ -1,65 +0,0 @@ -package gopher.channels - - -import akka.actor._ -import akka.pattern._ -import gopher._ - -import scala.concurrent._ -import scala.concurrent.duration._ -import scala.language.experimental.macros -import scala.language.postfixOps - -trait Channel[A] extends CloseableInputOutput[A,A] -{ - - thisChannel => - - def close(): Unit - - // override some operations - - class FilteredChannel(p:A=>Boolean) extends FilteredIOC(p) - with Channel[A] - { - override def close() = thisChannel.close() - } - - override def filter(p:A=>Boolean): Channel[A] = new FilteredChannel(p) - - trait CloseDelagate { - def close(): Unit = thisChannel.close() - } - - - - def compose(ch:Channel[A]):Channel[A] = new CompositionIOC[A](ch) with Channel[A] with CloseDelagate {} - - def expire(expireTime:FiniteDuration,capacity:Int = api.defaultExpireCapacity):Channel[A] = - { - val expireChannel = new ExpireChannel[A](expireTime,capacity,api) - expireChannel.compose(this) - } - -} - -object Channel -{ - - def apply[A](capacity: Int = 0)(implicit api:GopherAPI):Channel[A] = - { - require(capacity >= 0) - import api._ - val nextId = newChannelId - val futureChannelRef = (channelSupervisorRef.ask( - NewChannel(nextId, capacity) - )(10 seconds) - .asInstanceOf[Future[ActorRef]] - ) - - new ActorBackedChannel[A](futureChannelRef, api) - } - - - -} diff --git a/0.99.x/src/main/scala/gopher/channels/ChannelActor.scala b/0.99.x/src/main/scala/gopher/channels/ChannelActor.scala deleted file mode 100644 index 57c70519..00000000 --- a/0.99.x/src/main/scala/gopher/channels/ChannelActor.scala +++ /dev/null @@ -1,112 +0,0 @@ -package gopher.channels - -import akka.actor._ -import scala.language._ -import scala.concurrent._ -import scala.collection.immutable._ -import gopher._ - - -/** - * ChannelActor - actor, which leave - */ -abstract class ChannelActor[A](id:Long, api: GopherAPI) extends Actor { - - def receive = { - case cw@ContWrite(_, _, ft) => - val cwa = cw.asInstanceOf[ContWrite[A, cw.R]] - onContWrite(cwa) - case cr@ContRead(_, _, ft) => - val cra = cr.asInstanceOf[ContRead[A, cr.R]] - onContRead(cra) - case ccr@ClosedChannelRead(_) => - self ! ccr.cont - sender ! ChannelCloseProcessed(getNElements()) - case ccc@ChannelCloseCallback(cr) => - if (closed && readers.isEmpty) { - CloseableInput.applyDone[cr.R](cr)(api) - } else { - doneReaders = doneReaders :+ cr - } - case ChannelClose => - closed = true - stopIfEmpty - case ChannelRefDecrement => - nRefs -= 1 - if (nRefs == 0) { - stopAll - } - case ChannelRefIncrement => - nRefs += 1 - case GracefullChannelStop => - context.stop(self) - } - - protected[this] def onContWrite(cw: ContWrite[A, _]): Unit - - protected[this] def onContRead(cr: ContRead[A, _]): Unit - - protected[this] def getNElements(): Int - - protected[this] def processReaderClosed[B](reader: ContRead[A, B]): Boolean = - reader.function(reader) match { - case Some(f1) => api.continue(f1(ContRead.ChannelClosed), reader.flowTermination) - true - case None => false - } - - protected[this] def stopReaders(): Unit = { - while (!readers.isEmpty) { - val reader = readers.head - val c = reader.asInstanceOf[ContRead[A, reader.R]] - readers = readers.tail - c.function(c) foreach { f1 => - api.continue(f1(ContRead.ChannelClosed), c.flowTermination) - } - } - } - - protected[this] def stopWriters(): Unit = { - while (!writers.isEmpty) { - val writer = writers.head - val c = writer.asInstanceOf[ContWrite[A, writer.R]] - writers = writers.tail - c.function(c) foreach { - f1 => c.flowTermination.throwIfNotCompleted(new ChannelClosedException()) - } - } - } - - - def stopIfEmpty: Boolean - - def doClose(): Unit = - { - if (!closed) { - closed = true - } - while(!doneReaders.isEmpty) { - val dr = doneReaders.head - doneReaders=doneReaders.tail - CloseableInput.applyDone(dr)(api) - } - } - - def stopAll: Unit = - { - doClose() - if (!stopIfEmpty) { - // stop anyway - self ! GracefullChannelStop - } - } - - protected[this] implicit def ec: ExecutionContext = api.gopherExecutionContext - - protected[this] var closed=false - var readers = Queue[ContRead[A,_]] () - var writers = Queue[ContWrite[A,_]] () - var doneReaders = Queue[ContRead[Unit,_]]() - var nRefs = 1 - -} diff --git a/0.99.x/src/main/scala/gopher/channels/ChannelActorMessage.scala b/0.99.x/src/main/scala/gopher/channels/ChannelActorMessage.scala deleted file mode 100644 index bed6ea37..00000000 --- a/0.99.x/src/main/scala/gopher/channels/ChannelActorMessage.scala +++ /dev/null @@ -1,54 +0,0 @@ -package gopher.channels - -import gopher.FlowTermination -import gopher.channels.ContRead.In - -import scala.concurrent.Future -import scala.language.existentials - -sealed trait ChannelActorMessage - -case object ChannelClose extends ChannelActorMessage - -/** - * this is message wich send to ChannelActor, when we - * know, that channel is closed. In such case, we don't - * konw: is actor stopped or not, So, we say this message - * (instead read) and wait for reply. If reply is not received - * within given timeout: think that channel is-dead. - */ -case class ClosedChannelRead(cont: ContRead[_,_]) extends ChannelActorMessage - -/** - * this message is send, when all references to - * some instance of this channel are unreachable, - * so if we have no other instances (i.e. remote - * channel incarnation), than we must destroy channel. - **/ -case object ChannelRefDecrement extends ChannelActorMessage - -/** - * this message is send, when we create new remote - * reference to channel, backed by this actor. - **/ -case object ChannelRefIncrement extends ChannelActorMessage - -/** - * result of CloseChannelRead, return number of elements - * left to read - */ -case class ChannelCloseProcessed(nElements: Int) extends ChannelActorMessage - -/** - * add f to list of callbacks, called on channel close. - */ -case class ChannelCloseCallback[B](cr: ContRead[Unit,B]) - -/** - * When we decide to stop channel, do it via special message, - * to process one after messages, which exists now in queue. - * - * Note, that channel-stop messages can be send only from ChannelActor - */ -case object GracefullChannelStop extends ChannelActorMessage - diff --git a/0.99.x/src/main/scala/gopher/channels/ChannelProcessor.scala b/0.99.x/src/main/scala/gopher/channels/ChannelProcessor.scala deleted file mode 100644 index d6efe6a9..00000000 --- a/0.99.x/src/main/scala/gopher/channels/ChannelProcessor.scala +++ /dev/null @@ -1,42 +0,0 @@ -package gopher.channels - -import akka.actor._ -import scala.concurrent._ -import gopher._ - -class ChannelProcessor(api: GopherAPI) extends Actor -{ - - def receive = { - case Done(r,ft) => - if (!ft.isCompleted) { - ft.doExit(r) - } - case sk@Skip(f,ft) => if (!ft.isCompleted) { - try{ - f(sk) match { - case Some(cont) => { - val nowSender = sender - cont.foreach( nowSender ! _ ) - } - case None => /* do nothing */ - } - }catch{ - case ex: Throwable => ft.doThrow(ex) - } - } - case cr@ContRead(f,ch, ft) => - if (!ft.isCompleted) { - ch.cbread[cr.R](f,ft) - } - case cw@ContWrite(f,ch, ft) => - if (!ft.isCompleted) { - ch.cbwrite[cw.R]( f , ft) - } - case Never => /* do nothing */ - } - - - implicit val ec: ExecutionContext = api.gopherExecutionContext - -} diff --git a/0.99.x/src/main/scala/gopher/channels/ChannelSupervisor.scala b/0.99.x/src/main/scala/gopher/channels/ChannelSupervisor.scala deleted file mode 100644 index 162e3439..00000000 --- a/0.99.x/src/main/scala/gopher/channels/ChannelSupervisor.scala +++ /dev/null @@ -1,26 +0,0 @@ -package gopher.channels - -import akka.actor._ -import scala.concurrent._ -import gopher._ - -case class NewChannel(id: Long, capacity: Int) -case class CloseChannel(id: Long) - -class ChannelSupervisor(api: GopherAPI) extends Actor -{ - - def receive = { - case NewChannel(id,capacity) => - val actorClass = capacity match { - case 0 => classOf[UnbufferedChannelActor[_]] - case Int.MaxValue => classOf[GrowingBufferedChannelActor[_]] - case _ => classOf[BufferedChannelActor[_]] - } - val props = Props(actorClass,id, capacity, api) - sender ! context.actorOf(props, name=id.toString) - case CloseChannel(id) => - context.actorSelection(id.toString) ! ChannelClose - } - -} diff --git a/0.99.x/src/main/scala/gopher/channels/CloseableInput.scala b/0.99.x/src/main/scala/gopher/channels/CloseableInput.scala deleted file mode 100644 index 54e9c236..00000000 --- a/0.99.x/src/main/scala/gopher/channels/CloseableInput.scala +++ /dev/null @@ -1,35 +0,0 @@ -package gopher.channels - -import gopher.GopherAPI - -trait CloseableInput[A] extends Input[A] with DoneProvider[Unit] { - - closeableInputThis => - - trait DoneSignalDelegate[T] extends CloseableInput[T] { - val done = closeableInputThis.done - } - - override def filter(p: A=>Boolean): CloseableInput[A] = new Filtered(p) with DoneSignalDelegate[A] - - override def map[B](g: A=>B): CloseableInput[B] = new Mapped(g) with DoneSignalDelegate[B] - - - protected def applyDone[B](cr: ContRead[Unit,B]): Unit = { - CloseableInput.applyDone(cr)(api) - } - - -} - -object CloseableInput -{ - - def applyDone[B](cr: ContRead[Unit,B])(implicit api:GopherAPI): Unit = { - cr.function(cr) foreach { g => - api.continue(g(ContRead.In.value(())),cr.flowTermination) - } - } - - -} \ No newline at end of file diff --git a/0.99.x/src/main/scala/gopher/channels/Continuated.scala b/0.99.x/src/main/scala/gopher/channels/Continuated.scala deleted file mode 100644 index bca4837d..00000000 --- a/0.99.x/src/main/scala/gopher/channels/Continuated.scala +++ /dev/null @@ -1,134 +0,0 @@ -package gopher.channels; - -import gopher._ - -import scala.concurrent._ -import scala.language._ - -/** - * represent continuated computation from A to B. - */ -sealed trait Continuated[+A] -{ - type R = X forSome { type X <: A @annotation.unchecked.uncheckedVariance } -} - -sealed trait FlowContinuated[A] extends Continuated[A] -{ - def flowTermination: FlowTermination[A] -} - - -case class Done[A](result:A, override val flowTermination: FlowTermination[A]) extends FlowContinuated[A] - -/** - * read A and compute B as result. - */ -case class ContRead[A,B]( - function: ContRead[A,B] => - Option[ - ContRead.In[A] => Future[Continuated[B]] - ], - channel: Input[A], - override val flowTermination: FlowTermination[B]) extends FlowContinuated[B] -{ - type El = A - type S = B - type F = ContRead[A,B]=>Option[ContRead.In[A] => Future[Continuated[B]]] -} - -object ContRead -{ - - - sealed trait In[+A] - case class Value[+A](a:A) extends In[A] - case object Skip extends In[Nothing] - case object ChannelClosed extends In[Nothing] - case class Failure(ex:Throwable) extends In[Nothing] - - - object In - { - def value[A](a:A) = ContRead.Value(a) - def failure(ex:Throwable) = ContRead.Failure(ex) - def channelClosed = ContRead.ChannelClosed - def skip = ContRead.Skip - } - - @inline - def liftInValue[A,B](prev: ContRead[A,B])(f: Value[A] => Future[Continuated[B]] ): In[A] => Future[Continuated[B]] = - { - case v@Value(a) => f(v) - case Skip => Future successful prev - case ChannelClosed => prev.flowTermination.throwIfNotCompleted(new ChannelClosedException()) - Never.future - case Failure(ex) => prev.flowTermination.doThrow(ex) - Never.future - } - - @inline - def liftIn[A,B](prev: ContRead[A,B])(f: A => Future[Continuated[B]] ): In[A] => Future[Continuated[B]] = - { - // liftInValue(prev)(f(_.a)) - // we do ilining by hands instead. - case Value(a) => f(a) - case Skip => Future successful prev - case ChannelClosed => prev.flowTermination.throwIfNotCompleted(new ChannelClosedException()) - Never.future - case Failure(ex) => prev.flowTermination.doThrow(ex) - Never.future - } - - - def chainIn[A,B](prev: ContRead[A,B])(fn: (In[A], In[A] => Future[Continuated[B]]) => Future[Continuated[B]] ): - Option[In[A] => Future[Continuated[B]]] = - prev.function(prev) map (f1 => liftInValue(prev) { v => fn(v,f1) } ) - - type Aux[A,B] = ContRead[A,B]{ type El=A - type S=B - type F = ContRead[A,B]=>Option[ContRead.In[A]=>Future[Continuated[B]]] - } - - type AuxF[A,B] = ContRead[A,B]=>Option[ContRead.In[A]=>Future[Continuated[B]]] - - type AuxE[A] = ({type B; type L=ContRead[A,B]})#L -} - - -/** - * write A and compute B as result - */ -case class ContWrite[A,B](function: ContWrite[A,B] => Option[(A,Future[Continuated[B]])], channel: Output[A], override val flowTermination: FlowTermination[B]) extends FlowContinuated[B] -{ - type El = A - type F = ContWrite[A,B]=>Option[(A,Future[Continuated[B]])] -} - -object ContWrite -{ - type Aux[A,B] = ContWrite[A,B] - type AuxF[A,B] = ContWrite[A,B]=>Option[(A,Future[Continuated[B]])] -} - -/** - * skip (i.e. do some operation not related to reading or writing.) - */ -case class Skip[A](function: Skip[A] => Option[Future[Continuated[A]]], override val flowTermination: FlowTermination[A]) extends FlowContinuated[A] - -object Skip -{ - type AuxF[A] = Skip[A]=>Option[Future[Continuated[A]]] -} - - -/** - * never means the end of conversation - */ -case object Never extends Continuated[Nothing] -{ - val future = Future successful Never -} - - - diff --git a/0.99.x/src/main/scala/gopher/channels/CurrentFlowTermination.scala b/0.99.x/src/main/scala/gopher/channels/CurrentFlowTermination.scala deleted file mode 100644 index 20e4d178..00000000 --- a/0.99.x/src/main/scala/gopher/channels/CurrentFlowTermination.scala +++ /dev/null @@ -1,46 +0,0 @@ -package gopher.channels - -import scala.language.experimental.macros -import scala.reflect.macros.whitebox.Context -import scala.reflect.api._ -import gopher._ -import scala.concurrent._ -import scala.annotation._ - -object CurrentFlowTermination -{ - - - @compileTimeOnly("exit must be used only inside goScope or selector callbacks") - def exit[A](a: A): A = ??? - - def exitDelayed[A](a: A): A = - macro exitImpl[A] - - - def doThrow(e: Throwable): Unit = - macro doThrowImpl - - - def exitImpl[A](c:Context)(a: c.Expr[A])(implicit wtt: c.WeakTypeTag[A]): c.Expr[A]= - { - import c.universe._ - c.Expr[A](q""" - implicitly[_root_.gopher.FlowTermination[${wtt}]].doExit(${a}) - """) - } - - def doThrowImpl(c:Context)(e: c.Expr[Throwable]): c.Expr[Unit]= - { - import c.universe._ - c.Expr[Unit](q"implicitly[_root_.gopher.FlowTermination[Any]].doThrow(${e})") - } - - def shutdownImpl(c:Context)(): c.Expr[Unit] = - { - import c.universe._ - exitImpl[Unit](c)(c.Expr[Unit](q"implicitly[_root_.gopher.FlowTermination[Unit]].doExit(())")) - } - - -} diff --git a/0.99.x/src/main/scala/gopher/channels/DoneProvider.scala b/0.99.x/src/main/scala/gopher/channels/DoneProvider.scala deleted file mode 100644 index 002c86bf..00000000 --- a/0.99.x/src/main/scala/gopher/channels/DoneProvider.scala +++ /dev/null @@ -1,16 +0,0 @@ -package gopher.channels - -import gopher._ -import scala.concurrent._ - -trait DoneProvider[A] -{ - val done: Input[A] - - type done = done.read - - -} - - - diff --git a/0.99.x/src/main/scala/gopher/channels/EffectedChannel.scala b/0.99.x/src/main/scala/gopher/channels/EffectedChannel.scala deleted file mode 100644 index b3f924ce..00000000 --- a/0.99.x/src/main/scala/gopher/channels/EffectedChannel.scala +++ /dev/null @@ -1,84 +0,0 @@ -package gopher.channels - - -import gopher._ -import gopher.channels.ContRead.In -import gopher.util._ - -import scala.concurrent._ - -/* -trait EffectedChannel[A] extends Channel[A] with Effected[Channel[A]] -{ - thisEffectedChannel => - - def asInput(): EffectedInput[A] - def asOutput(): EffectedOutput[A] - - override val doneSignal: Input[Unit] = new Input[Unit] { - override def api: GopherAPI = thisEffectedChannel.api - - override def cbread[B](f: (ContRead[Unit, B]) => Option[(In[Unit]) => Future[Continuated[B]]], ft: FlowTermination[B]): Unit = - - - } - -} - - -object EffectedChannel -{ - def apply[A](in: Channel[A], policy: ThreadingPolicy): EffectedChannel[A] = - policy match { - case ThreadingPolicy.Single => new SinglethreadedEffectedChannel(in) - case ThreadingPolicy.Multi => new MultithreadedEffectedChannel(in) - } -} - - -class SinglethreadedEffectedChannel[A](ch:Channel[A]) extends SinglethreadedEffected[Channel[A]](ch) - with EffectedChannel[A] -{ - - def cbread[B](f: ContRead[A,B] => Option[ContRead.In[A] => Future[Continuated[B]]],ft: FlowTermination[B]): Unit = v.cbread(f,ft) - - def cbwrite[B](f: ContWrite[A,B] => Option[ - (A,Future[Continuated[B]]) - ], - ft: FlowTermination[B]): Unit = v.cbwrite(f,ft) - - def close() = v.close() - - def asInput() = api.makeEffectedInput(v, ThreadingPolicy.Single) - - def asOutput() = api.makeEffectedOutput(v, ThreadingPolicy.Single) - - def api: GopherAPI = v.api - - //override def filter(p:A=>Boolean):Channel[A] = new SinglethreadedEffectedChannel(v.filter(p)) - -} - -class MultithreadedEffectedChannel[A](ch:Channel[A]) extends MultithreadedEffected[Channel[A]](ch) - with EffectedChannel[A] -{ - - def cbread[B](f: ContRead[A,B] => Option[ContRead.In[A] => Future[Continuated[B]]],ft: FlowTermination[B]): Unit = v.get().cbread(f,ft) - - def cbwrite[B](f: ContWrite[A,B] => Option[ - (A,Future[Continuated[B]]) - ], - ft: FlowTermination[B]): Unit = v.get().cbwrite(f,ft) - - def close() = v.get().close() - - def asInput() = api.makeEffectedInput(v.get(), ThreadingPolicy.Multi) - - def asOutput() = api.makeEffectedOutput(v.get(), ThreadingPolicy.Multi) - - - def api: GopherAPI = v.get().api - -} - -*/ diff --git a/0.99.x/src/main/scala/gopher/channels/EffectedInput.scala b/0.99.x/src/main/scala/gopher/channels/EffectedInput.scala deleted file mode 100644 index e6deaa3d..00000000 --- a/0.99.x/src/main/scala/gopher/channels/EffectedInput.scala +++ /dev/null @@ -1,39 +0,0 @@ -package gopher.channels - -import gopher._ -import gopher.util._ -import scala.concurrent._ - -import scala.collection.mutable.{HashSet => MutableHashSet} -import java.util.concurrent.{ConcurrentHashMap=>JavaConcurrentHashMap} - -/* -trait EffectedInput[A] extends Input[A] with Effected[Input[A]] -{ - - def cbread[B](f: ContRead[A,B] => Option[ContRead.In[A] => Future[Continuated[B]]],ft: FlowTermination[B]): Unit = { - val sv = current - sv.cbread((cr:ContRead[A,B]) => if (sv==current) f(cr.copy(channel=this)) else None, ft) - } - - def api: GopherAPI = current.api - -} - - -object EffectedInput -{ - def apply[A](in: Input[A], policy: ThreadingPolicy): EffectedInput[A] = - policy match { - case ThreadingPolicy.Single => new SinglethreadedEffectedInput(in) - case ThreadingPolicy.Multi => new MultithreadedEffectedInput(in) - } -} - -class SinglethreadedEffectedInput[A](in:Input[A]) extends SinglethreadedEffected[Input[A]](in) - with EffectedInput[A] - -class MultithreadedEffectedInput[A](in:Input[A]) extends MultithreadedEffected[Input[A]](in) - with EffectedInput[A] - -*/ \ No newline at end of file diff --git a/0.99.x/src/main/scala/gopher/channels/EffectedOutput.scala b/0.99.x/src/main/scala/gopher/channels/EffectedOutput.scala deleted file mode 100644 index e4e654b5..00000000 --- a/0.99.x/src/main/scala/gopher/channels/EffectedOutput.scala +++ /dev/null @@ -1,38 +0,0 @@ -package gopher.channels - -import gopher._ -import gopher.util._ -import scala.concurrent._ - -trait EffectedOutput[A] extends Effected[Output[A]] with Output[A] -{ - - def cbwrite[B](f: ContWrite[A,B] => Option[ - (A,Future[Continuated[B]]) - ], - ft: FlowTermination[B]): Unit = { - val sv = current - sv.cbwrite[B](cw => if (current eq sv) f(cw.copy(channel=this)) else None,ft) - } - - def api: GopherAPI = current.api - -} - -object EffectedOutput -{ - def apply[A](in: Output[A], policy: ThreadingPolicy): EffectedOutput[A] = - policy match { - case ThreadingPolicy.Single => new SinglethreadedEffectedOutput(in) - case ThreadingPolicy.Multi => new MultithreadedEffectedOutput(in) - } -} - -class SinglethreadedEffectedOutput[A](out:Output[A]) extends SinglethreadedEffected[Output[A]](out) - with EffectedOutput[A] - -class MultithreadedEffectedOutput[A](out:Output[A]) extends MultithreadedEffected[Output[A]](out) - with EffectedOutput[A] - - - diff --git a/0.99.x/src/main/scala/gopher/channels/ExpireChannel.scala b/0.99.x/src/main/scala/gopher/channels/ExpireChannel.scala deleted file mode 100644 index f627558f..00000000 --- a/0.99.x/src/main/scala/gopher/channels/ExpireChannel.scala +++ /dev/null @@ -1,82 +0,0 @@ -package gopher.channels - -import gopher.{FlowTermination, GopherAPI} - -import scala.language.postfixOps -import scala.concurrent.duration.FiniteDuration -import scala.concurrent.{Future, Promise} - - - -class ExpireChannel[A](expire: FiniteDuration, capacity:Int, gopherApi: GopherAPI) extends Channel[A] { - - case class Element(value:A, readed:Promise[Boolean], time: Long) - - val internal = api.makeChannel[Element](capacity) - - - /** - * apply f, when input will be ready and send result to API processor - */ - override def cbread[B](f: (ContRead[A, B]) => Option[(ContRead.In[A]) => Future[Continuated[B]]], ft: FlowTermination[B]): Unit = - { - - def fw(cr: ContRead[Element,B]):Option[ContRead.In[Element] => Future[Continuated[B]]] = { - f(ContRead(f,this,ft)) map { g => { - case ContRead.Value(e) => - if (e.readed.trySuccess(true)) { - if (System.currentTimeMillis() - expire.toMillis > e.time) { - g(ContRead.Skip) - } else { - g(ContRead.Value(e.value)) - } - } else { - g(ContRead.Skip) - } - case ContRead.Skip => g(ContRead.Skip) - case ContRead.ChannelClosed => g(ContRead.ChannelClosed) - case ContRead.Failure(ex) => g(ContRead.Failure(ex)) - } - } - } - internal.cbread[B](fw,ft) - } - - /** - * apply f and send result to channels processor. - */ - override def cbwrite[B](f: (ContWrite[A, B]) => Option[(A, Future[Continuated[B]])], ft: FlowTermination[B]): Unit = - { - val p = Promise[Boolean] - implicit val ec = api.gopherExecutionContext - api.actorSystem.scheduler.scheduleOnce(expire){ - if (p.trySuccess(true)) { - val e = internal.aread - // skip one. - } - } - def fw(cw:ContWrite[Element,B]):Option[(Element,Future[Continuated[B]])]= - { - f(ContWrite(f,this,ft)) map { case (a,n) => - (Element(a,p,System.currentTimeMillis()),n) - } - } - internal.cbwrite(fw,ft) - } - - override val done: Input[Unit] = internal.done - - override def api: GopherAPI = gopherApi - - override def close(): Unit = internal.close() - - -} - -object ExpireChannel -{ - - def apply[A](expireTime:FiniteDuration,capacity:Int)(implicit api:GopherAPI):ExpireChannel[A] = - new ExpireChannel[A](expireTime,capacity,api) - -} \ No newline at end of file diff --git a/0.99.x/src/main/scala/gopher/channels/FoldSelectorBuilder.scala b/0.99.x/src/main/scala/gopher/channels/FoldSelectorBuilder.scala deleted file mode 100644 index 343195b3..00000000 --- a/0.99.x/src/main/scala/gopher/channels/FoldSelectorBuilder.scala +++ /dev/null @@ -1,802 +0,0 @@ -package gopher.channels - -import java.util.concurrent.atomic.AtomicInteger - -import scala.language.experimental.macros -import scala.reflect.macros.blackbox.Context -import scala.reflect.api._ -import gopher._ -import gopher.util._ - -import scala.concurrent._ -import scala.concurrent.duration._ -import scala.annotation.unchecked._ -import java.util.function.{BiConsumer => JBiConsumer} - -import scala.collection.mutable -import scala.collection.mutable.ArrayBuffer - - -abstract class FoldSelectorBuilder[T](nCases:Int) extends SelectorBuilder[T] -{ - - type R = T - - type HandleFunction[A] = (ExecutionContext, FlowTermination[T],A) => Future[T] - - - def reading[A](ch: Input[A])(f: A=>T): FoldSelectorBuilder[T] = - macro SelectorBuilder.readingImpl[A,T,FoldSelectorBuilder[T]] - - def readingFoldEffected[A](ch:Input[A],projIndex:Int)(f: A=>T): FoldSelectorBuilder[T] = - macro FoldSelectorBuilderImpl.readingFoldEffected[A,T,FoldSelectorBuilder[T]] - - def readingFoldEffectedWithFlowTerminationAsync[A](ch:Input[A], i:Int, - f: (ExecutionContext, FlowTermination[T], A) => Future[T] - ): this.type = - { - handleFunctions(i)=f - inputIndices.put(i,ch) - selector.addReader[A](ch,normalizedDispatchReader[A]) - this - } - - def readingWithFlowTerminationAsync[A](ch: Input[A], - f: (ExecutionContext, FlowTermination[T], A) => Future[T]):this.type = - { - withReader[A](ch, normalizedPlainReader(f,ch)) - } - - def writing[A](ch: Output[A],x:A)(f: A=>T): FoldSelectorBuilder[T] = - macro SelectorBuilder.writingImpl[A,T,FoldSelectorBuilder[T]] - - def writingFoldEffected[A](ch: Output[A], projIndex: Int, x:A)(f: A=>T): FoldSelectorBuilder[T] = - macro FoldSelectorBuilderImpl.writingFoldEffected[A,T,FoldSelectorBuilder[T]] - - - @inline - def writingFoldEffectedWithFlowTerminationAsync[A](ch:Output[A], x: =>A, i: Int, - f: (ExecutionContext, FlowTermination[T], A) => Future[T] - ): this.type = { - handleFunctions(i)=f - handleOutputVars(i) = (()=>x) - outputIndices.put(i,ch) - val dispathWrite = normalizedDispatchWriter[A] - selector.addWriter(ch,dispathWrite) - this - } - - - @inline - def writingWithFlowTerminationAsync[A](ch:Output[A], x: =>A, - f: (ExecutionContext, FlowTermination[T], A) => Future[T]): this.type = { - withWriter[A](ch, normalizedWriter(f,x,ch)) - } - - def timeout(t:FiniteDuration)(f: FiniteDuration => T): FoldSelectorBuilder[T] = - macro SelectorBuilder.timeoutImpl[T,FoldSelectorBuilder[T]] - - @inline - def timeoutWithFlowTerminationAsync(t:FiniteDuration, - f: (ExecutionContext, FlowTermination[T], FiniteDuration) => Future[T] - ): this.type = - withTimeout(t){ sk => Some(f(ec,sk.flowTermination,t) map Function.const(sk) ) } - - - def idle(body:T): FoldSelectorBuilder[T] = - macro SelectorBuilder.idleImpl[T,FoldSelectorBuilder[T]] - - - val inputIndices: IntIndexedCounterReverse[Input[_]] = new IntIndexedCounterReverse(nCases) - val outputIndices: IntIndexedCounterReverse[Output[_]] = new IntIndexedCounterReverse(nCases) - val handleFunctions: Array[HandleFunction[_]] = new Array(nCases) - val handleOutputVars: Array[ () => _ ] = new Array(nCases) - - def reregisterInputIndices(): Unit = - { - inputIndices.foreachIndex{(i,cr) => - if (cr.counter <= 0) { - cr.counter += 1 - val input = inputIndices.get(i).get.value - val typedInput: Input[input.read] = input.asInstanceOf[Input[input.read]] - val reader = normalizedDispatchReader[input.read] - val fun = selector.lockedRead(reader,typedInput,selector) - typedInput.cbread(fun,selector) - } - } - } - - def reregisterOutputIndices():Unit = - { - outputIndices.foreachIndex{(i,cw) => - if (cw.counter <= 0) { - cw.counter += 1 - val output = outputIndices.get(i).get.value - val typedOutput:Output[output.write] = output.asInstanceOf[Output[output.write]] - val writer = normalizedDispatchWriter[output.write] - val fun = selector.lockedWrite(writer,typedOutput,selector) - typedOutput.cbwrite(fun,selector) - } - } - } - - def reregisterIndices():Unit = - { - reregisterInputIndices() - reregisterOutputIndices() - } - - trait NormalizedDispatch - - - def normalizedPlainReader[A](f:HandleFunction[A], ch:Input[A]):ContRead.AuxF[A,T]= - { - def nf(prev:ContRead[A,T]):Option[ContRead.In[A]=>Future[Continuated[T]]] = Some{ - case ContRead.Value(a) => f(ec,selector,a) map { _ => ContRead[A,T](nf,ch,selector) } - case ContRead.Skip => { Future successful ContRead[A,T](nf,ch,selector) } - case ContRead.ChannelClosed => prev.flowTermination.throwIfNotCompleted(new ChannelClosedException()) - Never.future - case ContRead.Failure(ex) => prev.flowTermination.throwIfNotCompleted(ex) - Never.future - } - nf - } - - class NormalizedDispatchReader[A] extends ContRead.AuxF[A,T] with NormalizedDispatch - { - - override def apply(prev: ContRead[A, T]): Option[(ContRead.In[A]) => Future[Continuated[T]]] = - { - val ch = prev.channel //match { - //case fe: FoldSelectorEffectedInput[_,_] => - // System.err.println(s"normalizedEffectedReader:fromEffected ${fe.current} ${fe.index} fe=${fe} locked=${selector.isLocked}") - // fe.current - //case _ => prev.channel - //} - val i = inputIndices.refIndexOf(ch) - //System.err.println(s"normalizedEffectedReader ch=$ch i=$i locked=${selector.isLocked}") - if (i == -1) - None - else { - inputIndices.values(i).counter -= 1 - val ff = handleFunctions(i).asInstanceOf[HandleFunction[A]] - Some { - case ContRead.Value(a) => ff(ec, selector, a) map { _ => reregisterIndices(); Never } - case ContRead.Skip => { - reregisterIndices() - Future successful Never - } - case ContRead.ChannelClosed => prev.flowTermination.throwIfNotCompleted(new ChannelClosedException()) - Never.future - case ContRead.Failure(ex) => prev.flowTermination.throwIfNotCompleted(ex) - Never.future - } - } - } - - } - - def normalizedDispatchReader[A]:ContRead.AuxF[A,T]= { - // return never, becouse next step is generated via reregisterInputIndi - new NormalizedDispatchReader[A] - } - - - class NormalizedDispatchWriter[A] extends (ContWrite[A,T]=>Option[(A,Future[Continuated[T]])]) - with NormalizedDispatch - { - - override def apply(prev: ContWrite[A, T]): Option[(A, Future[Continuated[T]])] = - { - val i = outputIndices.refIndexOf(prev.channel) - if (i == -1) - None - else { - outputIndices.values(i).counter -= 1 - val ff = handleFunctions(i).asInstanceOf[HandleFunction[A]] - val xn = handleOutputVars(i).asInstanceOf[()=>A].apply() - Some((xn,ff(ec,prev.flowTermination,xn) map { _ => reregisterIndices(); Never } )) - } - } - - - } - - def normalizedDispatchWriter[A]:ContWrite.AuxF[A,T] = - { - new NormalizedDispatchWriter[A] - } - - def normalizedWriter[A](f:HandleFunction[A],x: =>A, ch:Output[A]):ContWrite.AuxF[A,T]= { - def nf(prev: ContWrite[A, T]): Option[(A, Future[Continuated[T]])] = { - val xn = x - Some(xn, f(ec, prev.flowTermination, xn) map (_ => ContWrite(nf, ch, this.selector))) - } - nf - } - - - def beforeRefresh(): Unit = - { - // inputIndices.clear() - // outputIndices.clear() - } - - def handleError(f: Throwable => T): FoldSelectorBuilder[T] = - macro SelectorBuilder.handleErrorImpl[T,FoldSelectorBuilder[T]] - - @inline - def handleErrorWithFlowTerminationAsync(f: (ExecutionContext, FlowTermination[T], Continuated[T], Throwable) => Future[T] ): this.type = - withError{ (ec,ft,cont,ex) => - f(ec,ft,cont,ex).map{ x => - cont match { - case c: NormalizedDispatch => - reregisterIndices() - Never - case other => other - } - }(ec) - } - - -} - -/** - * Short name for use in fold signature - **/ -class FoldSelect[T](sf:SelectFactory, nCases: Int) extends FoldSelectorBuilder[T](nCases) -{ - override def api = sf.api - - - -} - - -class FoldSelectorBuilderImpl(override val c:Context) extends SelectorBuilderImpl(c) { - - import c.universe._ - - - class FoldActionGenerator(fp: FoldParse) extends ActionGenerator { - - override def genReading(builder: TermName, channel: Tree, param: ValDef, body: Tree): Tree = { - require(!(channel.symbol eq null)) - if (fp.stateSelectRole.active) { - if (channel.symbol == fp.stateVal.symbol) { - val clearedChannel = Ident(fp.stateVal.name) - q"${builder}.readingFoldEffected(${clearedChannel},0){$param => $body}" - } else { - defaultActionGenerator.genReading(builder, channel, param, body) - } - } else { - fp.projectionsBySym.get(channel.symbol) match { - case None => defaultActionGenerator.genReading(builder, channel, param, body) - case Some(proj) => - val (ch, index) = genProjChannelIndex(fp,proj) - q"${builder}.readingFoldEffected($ch,$index){$param => $body}" - } - } - } - - override def genWriting(builder: TermName, channel: Tree, expr: Tree, param: ValDef, body: Tree): c.universe.Tree = { - if (fp.stateSelectRole.active) { - if (channel.symbol == fp.stateVal.symbol) { - val clearedChannel = Ident(fp.stateVal.name) - q"${builder}.writingFoldEffected($clearedChannel, 0, $expr){$param => $body}" - } else { - defaultActionGenerator.genWriting(builder, channel, expr, param, body) - } - } else { - fp.projectionsBySym.get(channel.symbol) match { - case None => defaultActionGenerator.genWriting(builder, channel, expr, param, body) - case Some(proj) => - val (ch, index) = genProjChannelIndex(fp,proj) - q"${builder}.writingFoldEffected($ch, $index, $expr){$param => $body}" - } - } - } - - override def genDone(builder: TermName, channel: Tree, param: ValDef, body: Tree): Tree = - { - if (fp.stateSelectRole.active) { - if (channel.symbol == fp.stateVal.symbol) { - val clearedChannel = Ident(fp.stateVal.name) - q"${builder}.onDoneFoldEffected($clearedChannel, 0){$param => $body}" - } else { - defaultActionGenerator.genDone(builder,channel,param,body) - } - } else { - fp.projectionsBySym.get(channel.symbol) match { - case None => defaultActionGenerator.genDone(builder, channel, param, body) - case Some(proj) => - val (ch, index) = genProjChannelIndex(fp,proj) - q"${builder}.onDoneFoldEffected($ch, $index){$param => $body}" - } - } - } - - override def genError(builder: TermName, selector: Tree, param: ValDef, body: Tree): Tree = { - defaultActionGenerator.genError(builder,selector,param,body) - } - - def genProjChannelIndex(fp:FoldParse, proj: (FoldParseProjection, Int)): (Tree,Int) = - { - val i = proj._2 - val ch = makeProj(fp.stateVal.name,i) - (ch,i) - } - - } - - /** - * ``` - * selector.afold(s0) { (s, selector) => - * selector match { - * case x1: in1.read => f1 - * case x2: in2.read => f2 - * case x3: out3.write if (x3==v) => f3 - * case _ : in,d - * case _ => f4 - * } - * } - * ``` - * will be transformed to - * {{{ - * var s = s0 - * val bn = new FoldSelector(3) - * bn.reading(in1)(x1 => f1 map {s=_; s }) - * bn.reading(in2)(x2 => f2 map {s=_; s }) - * bn.writing(out3,v)(x2 => f2 map {s=_; s}) - * bn.idle(f4 map {s=_; s}) - * }}} - * - * also supported partial function syntax: - * - * {{{ - * selector.afold((0,1)){ - * case ((x,y),s) => s match { - * case x1: in1.read => f1 - * case x2: in2.read => f2 - * case x3: out3.write if (x3==v) => f3 - * case _ => f4 - * } - * }}} - * will be transformed to: - * {{{ - * var s = s0 - * val bn = new FoldSelector(3) - * bn.reading(in1)(x1 => async{ val x = s._1; - * val y = s._2; - * s = f1; s} }) - * bn.reading(in2)(x2 => { val x = s._1; - * val y = s._2; - * s=f2; s} }) - * bn.writing(out3,v[x/s._1;y/s._2]) - * (x2 => s=f2; s}) - * }}} - * - * Using channels as part of fold state: - * {{{ - * selector.afold(ch){ case (ch,s) => - * s match { - * case x: ch.read => generated.write(x) - * ch.filter(_ % x == 0) - * } - * } - * }}} - * will be transformed to - * {{{ - * var s = ch - * val bn = new FoldSelector - * //val ef = new FoldSelectorEffectedInput(()=>s) - * bn.readingFoldEffected(0)(x => async{ generated.write(x) - * s.filter(_ % x == 0)}) - * }}} - **/ - def afold[S: c.WeakTypeTag](s: c.Expr[S])(op: c.Expr[(S, FoldSelect[S]) => S]): c.Expr[Future[S]] = { - val foldParse = parseFold(op) - val sName = foldParse.stateVal.name - val sNameStable = TermName(c.freshName("s")) - val bn = TermName(c.freshName("fold")) - val ncases = foldParse.selectCases.map(preTransformCaseDef(foldParse, bn, _, sNameStable)) - val tree = Block( - atPos(s.tree.pos)(q"var $sName = ${s}") :: - (q"val $sNameStable = $sName") :: - q"val ${bn} = new gopher.channels.FoldSelect[${weakTypeOf[S]}](${c.prefix},${ncases.length})" :: - //wrapInEffected(foldParse, bn, transformSelectMatch(bn, ncases, new FoldActionGenerator(foldParse))), - transformSelectMatch(bn, ncases, new FoldActionGenerator(foldParse)), - q"${bn}.go" - ) - c.Expr[Future[S]](tree) - } - - def fold[S: c.WeakTypeTag](s: c.Expr[S])(op: c.Expr[(S, FoldSelect[S]) => S]): c.Expr[S] = - c.Expr[S](q"scala.async.Async.await(${afold(s)(op).tree})") - - sealed trait SelectRole { - def active: Boolean - - def generateRefresh(selector: TermName, state: TermName, i: Int): Option[c.Tree] - } - - object SelectRole { - - - case object NoParticipate extends SelectRole { - def active = false - - def generateRefresh(selector: TermName, state: TermName, i: Int) = None - } - - case object Read extends SelectRole { - def active = true - - def generateRefresh(selector: TermName, state: TermName, i: Int) = - Some(q"$selector.inputIndices.put(${if (i < 0) 0 else i},${makeProj(state, i)})") - } - - case object Write extends SelectRole { - def active = true - - def generateRefresh(selector: TermName, state: TermName, i: Int) = - Some(q"$selector.outputIndices.put(${if (i < 0) 0 else i},${makeProj(state, i)})") - - } - - - } - - - case class FoldParseProjection( - sym: c.Symbol, - selectRole: SelectRole - ) - - case class FoldParse( - stateVal: ValDef, - stateSelectRole: SelectRole, - projections: List[FoldParseProjection], - selectValName: c.TermName, - selectCases: List[CaseDef] - ) { - lazy val projectionsBySym: Map[c.Symbol, (FoldParseProjection, Int)] = - projections.zipWithIndex.foldLeft(Map[c.Symbol, (FoldParseProjection, Int)]()) { (s, e) => - s.updated(e._1.sym, e) - } - } - - def withProjAssignments(fp: FoldParse, patSymbol: Symbol, body: c.Tree): c.Tree = { - val stateName = fp.stateVal.name - val projAssignments = (fp.projections.zipWithIndex) map { - case (FoldParseProjection(sym, usedInSelect), i) => - val pf = TermName("_" + (i + 1).toString) - q"val ${sym.name.toTermName} = $stateName.$pf" - } - val projectedSymbols = fp.projections.map(_.sym).toSet - val nbody = cleanIdentsSubstEffected(fp, body, projectedSymbols + fp.stateVal.symbol + patSymbol) - if (projAssignments.isEmpty) - nbody - else { - Block(projAssignments, cleanIdentsSubstEffected(fp, nbody, projectedSymbols)) - } - } - - private def cleanIdentsSubstEffected(fp: FoldParse, tree: c.Tree, symbols: Set[Symbol]): Tree = { - val tr = new Transformer { - override def transform(tree: c.Tree): c.Tree = - tree match { - case Ident(s) => if (symbols.contains(tree.symbol)) { - // create new tree without associated symbol. - //(typer wil reassociate one). - atPos(tree.pos)(Ident(s)) - } else { - super.transform(tree) - } - case ValDef(m, s, rhs, lhs) => if (symbols.contains(tree.symbol)) { - atPos(tree.pos)(ValDef(m, s, rhs, lhs)) - super.transform(tree) - } else { - super.transform(tree) - } - case _ => super.transform(tree) - } - } - tr.transform(tree) - } - - def substProj(foldParse: FoldParse, newName: c.TermName, body: c.Tree, substEffected: Boolean, debug: Boolean): c.Tree = { - val projections = foldParse.projections - val stateSymbol = foldParse.stateVal.symbol - val pi = projections.map(_.sym).zipWithIndex.toMap - //val sName = stateSymbol.name.toTermName - val sName = newName - val transformer = new Transformer() { - override def transform(tree: Tree): Tree = - tree match { - case Ident(name) => pi.get(tree.symbol) match { - case Some(n) => - if (substEffected && projections(n).selectRole.active) { - //val proj = makeEffectedName(projections(n).sym) - //atPos(tree.pos)(q"${proj}") - tree // will be changed (fully elimitated) later by processing of actionGenerator in transformCaseDed - } else { - atPos(tree.pos)(makeProj(sName,n)) - } - case None => - if (tree.symbol eq stateSymbol) { - if (substEffected && foldParse.stateSelectRole.active) { - //val en = makeEffectedName(stateSymbol) - //atPos(tree.pos)(Ident(en)) - tree - } else { - atPos(tree.pos)(Ident(sName)) - } - } else { - super.transform(tree) - } - } - case t@Typed(expr, tpt) => - tpt match { - case tptt: TypeTree => - tptt.original match { - case Select(base, name) => - //tptt.setOriginal(tranform(tptt.original)) - Typed(expr, transform(tptt.original)) - //val trOriginal = transform(tptt.original) - //Typed(expr,internal.setOriginal(tptt,trOriginal)) - //Typed(expr,tq"${sName}.read") - case _ => - super.transform(tree) - } - case _ => - super.transform(tree) - } - - case _ => super.transform(tree) - } - } - return transformer.transform(body) - } - - def makeProj(name: TermName, n: Int): c.Tree = - { - if (n == -1) { - q"${name}" - }else { - val proj = TermName("_" + (n + 1).toString) - (q"${name}.${proj}") - } - } - - def preTransformCaseDefBody(fp:FoldParse, foldSelect: TermName, patSymbol: Symbol, body:c.Tree):c.Tree = - { - val sName = fp.stateVal.name - val tmpName = TermName(c.freshName("tmp")) - val refresh = refreshEffected(fp, foldSelect) - val statements = List( - q"val $tmpName = ${withProjAssignments(fp,patSymbol,body)}", - q"$sName = $tmpName" - ) ++ - refresh ++ List( - q"$sName" - ) - q"{..$statements}" - } - - - def beforeRefresh(foldSelect: TermName):c.Tree = - q"${foldSelect}.beforeRefresh()" - - def refreshEffected(fp:FoldParse, foldSelect:TermName):List[c.Tree] = - { - if (fp.stateSelectRole.active) { - beforeRefresh(foldSelect):: - fp.stateSelectRole.generateRefresh(foldSelect, fp.stateVal.name,-1).get::Nil - }else{ - val r = fp.projections.zipWithIndex.filter(_._1.selectRole.active).flatMap{ case (proj,i) => - proj.selectRole.generateRefresh(foldSelect,fp.stateVal.name,i) - } - if (r.nonEmpty) { - beforeRefresh(foldSelect)::r - }else{ - List() - } - } - } - - - def preTransformCaseDef(fp:FoldParse, foldSelect: TermName, cd:CaseDef,stableName:TermName):CaseDef = - { - val patSymbol = cd.pat.symbol - - val acceptor = new SelectCaseDefAcceptor[FoldParse,(Tree, Tree)] { - - - override def onRead(s: FoldParse, v: TermName, ch: Tree, tp: Tree): (Tree, Tree) = { - // TODO: parse something like ch.done.read - if (fp.projections.indexWhere(_.sym.name == v) != -1) { - //low-priority: implement shadowing instead abort - c.abort(cd.pat.pos,"read variable shadow fold state") - } - (cd.pat,cd.guard) - } - - override def onWrite(s: FoldParse, v: TermName, expression: Tree, ch: Tree, tp: Tree): (Tree, Tree) = { - val idx = fp.projections.indexWhere(_.sym.name == v) - if (idx != -1) { - val newName = TermName(c.freshName("wrt")) - val newPat = atPos(cd.pat.pos)(Bind(newName, Typed(Ident(v),tp))) - if (!cd.guard.isEmpty) { - c.abort(cd.pos, "guard must be empty"); - } - val sName = fp.stateVal.name.toTermName - val proj = TermName("_" + (idx + 1)) - val newGuard = q"${newName} == $sName.$proj" - (newPat, newGuard) - }else { - (cd.pat, cd.guard) - } - } - - override def onSelectTimeout(s: FoldParse, v: TermName, select: Tree, tp: Tree): (Tree, Tree) = - { - (cd.pat, cd.guard) - } - - override def onIdle(s: FoldParse): (Tree, Tree) = { - (cd.pat, cd.guard) - } - - override def onDone(s: FoldParse, v: TermName, ch: Tree, tp: Tree): (Tree, Tree) = { - if (fp.projections.indexWhere(_.sym.name == v) != -1) { - c.abort(cd.pat.pos,"done variable shadow fold state") - } - (cd.pat, cd.guard) - } - - override def onError(s: FoldParse, v: TermName, select: Tree, tp: Tree): (Tree, Tree) = { - (cd.pat, cd.guard) - } - - } - - val (pat, guard) = acceptSelectCaseDefPattern(cd, fp, acceptor) - - val symName = fp.stateVal.symbol.name.toTermName - atPos(cd.pos)(CaseDef(substProj(fp,stableName,pat,true,false), - substProj(fp,symName,guard,false,false), - preTransformCaseDefBody(fp,foldSelect,patSymbol,cd.body))) - } - - def parseFold[S](op: c.Expr[(S,FoldSelect[S])=>S]): FoldParse = - { - op.tree match { - case Function(List(x,y),Match(choice,cases)) => - val ValDef(_,yName,_,_) = y - if (choice.symbol != y.symbol) { - if (cases.length == 1) { - cases.head match { - case CaseDef(Apply(TypeTree(), - List(Apply(TypeTree(),params),Bind(sel,_))), - guard, - Match(Ident(choice1),cases1)) => - if (sel == choice1) { - val selectSymbols = retrieveSelectChannels(cases1) - FoldParse( - stateVal = x, - stateSelectRole = selectSymbols.getOrElse(x.symbol,SelectRole.NoParticipate), - projections = params map { x=> val sym = x.symbol - FoldParseProjection(sym,selectSymbols.getOrElse(sym,SelectRole.NoParticipate)) - }, - selectValName = sel.toTermName, - selectCases = cases1 - ) - } else { - c.abort(op.tree.pos,"expected shap like {case (a,s) => s match { ... } }") - } - case _ => - c.abort(op.tree.pos,"match agains selector in pf is expected") - } - } else { - c.abort(op.tree.pos,"partial function in fold must have one case") - } - } else { - val selectorName = choice match { - case Ident(sel) => sel - } - if (selectorName == yName) { - val selectSymbols = retrieveSelectChannels(cases) - FoldParse( - stateVal = x, - stateSelectRole = selectSymbols.getOrElse(x.symbol,SelectRole.NoParticipate), - projections = List(), - selectValName = selectorName.toTermName, - selectCases = cases - ) - } else { - c.abort(op.tree.pos,"expected choice over selector in fold") - } - } - // TODO: check that 'choice' == 'y' - case Function(params,something) => - c.abort(op.tree.pos,"match is expected in select.fold, we have: "+MacroUtil.shortString(c)(op.tree)) - case _ => - c.abort(op.tree.pos,"inline function is expected in select.fold, we have: "+MacroUtil.shortString(c)(op.tree)) - } - } - - private def retrieveSelectChannels(cases:List[CaseDef]): Map[Symbol,SelectRole] = - { - val s0=Map[Symbol,SelectRole]() - val acceptor = new SelectCaseDefAcceptor[Map[Symbol,SelectRole],Map[Symbol,SelectRole]] { - override def onRead(s: Map[Symbol, SelectRole], v: TermName, ch: Tree, tp:Tree): Map[Symbol, SelectRole] = - s.updated(ch.symbol,SelectRole.Read) - - override def onWrite(s: Map[c.Symbol, SelectRole], v: TermName, expr :c.Tree, ch: c.Tree, tp: Tree): Map[Symbol, SelectRole] = - s.updated(ch.symbol,SelectRole.Write) - - override def onSelectTimeout(s: Map[Symbol, SelectRole], v: TermName, select: Tree, tp: Tree): Map[Symbol, SelectRole] = s - - override def onIdle(s: Map[Symbol, SelectRole]): Map[Symbol, SelectRole] = s - - override def onDone(s: Map[Symbol, SelectRole], v:TermName, ch: Tree, tp: Tree): Map[Symbol, SelectRole] = { - val doneChannel = c.typecheck(q"${ch}.done") - s.updated(doneChannel.symbol, SelectRole.Read) - } - - override def onError(s: Map[Symbol, SelectRole], v: TermName, select: Tree, tp: Tree): Map[Symbol, SelectRole] = s - - } - cases.foldLeft(s0){ (s,e) => - acceptSelectCaseDefPattern(e,s,acceptor) - } - } - - - - - def readingFoldEffected[A,B:c.WeakTypeTag,S](ch:c.Expr[Input[A]], projIndex :c.Expr[Int])(f:c.Expr[A=>B]):c.Expr[S] = - { - import c.universe._ - f.tree match { - case Function(valdefs, body) => - //val effectedName = ch.tree match { - // case Ident(name) => makeEffectedName(ch.tree.symbol) - // case _ => c.abort(f.tree.pos, s"Identifier expected, we have ${ch.tree}") - //} - - SelectorBuilder.buildAsyncCall[B,S](c)(valdefs,body, - { (nvaldefs, nbody) => - q"""${c.prefix}.readingFoldEffectedWithFlowTerminationAsync(${ch},${projIndex}, - ${Function(nvaldefs,nbody)} - ) - """ - }) - case _ => c.abort(c.enclosingPosition,"argument of reading.apply must be function") - } - } - - def writingFoldEffected[A,T:c.WeakTypeTag,S](ch:c.Expr[Output[A]], projIndex: c.Expr[Int], x:c.Expr[A])(f:c.Expr[A=>T]):c.Expr[S] = - { - import c.universe._ - f.tree match { - case Function(valdefs, body) => - val retval = SelectorBuilder.buildAsyncCall[T,S](c)(valdefs,body, - { (nvaldefs, nbody) => - q"""${c.prefix}.writingFoldEffectedWithFlowTerminationAsync(${ch},${x}, ${projIndex}, - ${Function(nvaldefs,nbody)} - ) - """ - }) - retval - case _ => c.abort(c.enclosingPosition,"second argument of writing must have shape Function(x,y)") - } - } - - - - -} - - diff --git a/0.99.x/src/main/scala/gopher/channels/ForeverSelectorBuilder.scala b/0.99.x/src/main/scala/gopher/channels/ForeverSelectorBuilder.scala deleted file mode 100644 index c15530e2..00000000 --- a/0.99.x/src/main/scala/gopher/channels/ForeverSelectorBuilder.scala +++ /dev/null @@ -1,122 +0,0 @@ -package gopher.channels - -import scala.language.experimental.macros -import scala.reflect.macros.blackbox.Context -import scala.reflect.api._ -import gopher._ -import gopher.util._ -import scala.concurrent._ -import scala.concurrent.duration._ -import scala.annotation.unchecked._ - - -/** - * Builder for 'forever' selector. Can be obtained as `gopherApi.select.forever`. - **/ -trait ForeverSelectorBuilder extends SelectorBuilder[Unit] -{ - - - def reading[A](ch: Input[A])(f: A=>Unit): ForeverSelectorBuilder = - macro SelectorBuilder.readingImpl[A,Unit,ForeverSelectorBuilder] - // internal error in compiler when using this.type as S - - - def readingWithFlowTerminationAsync[A](ch: Input[A], f: (ExecutionContext, FlowTermination[Unit], A) => Future[Unit] ): this.type = - { - lazy val cont = ContRead(normalized, ch, selector) - def normalized(_cont:ContRead[A,Unit]):Option[ContRead.In[A]=>Future[Continuated[Unit]]] = { - Some(ContRead.liftIn(_cont)(a => f(ec, selector, a) map Function.const(cont))) - } - - withReader[A](ch, normalized) - } - - def writing[A](ch: Output[A], x: A)(f: A => Unit): ForeverSelectorBuilder = - macro SelectorBuilder.writingImpl[A,Unit,ForeverSelectorBuilder] - - @inline - def writingWithFlowTerminationAsync[A](ch:Output[A], x: =>A, f: (ExecutionContext, FlowTermination[Unit], A) => Future[Unit] ): ForeverSelectorBuilder = - withWriter[A](ch, { cw => Some(x,f(ec,cw.flowTermination, x) map Function.const(cw)) } ) - - def timeout(t:FiniteDuration)(f: FiniteDuration => Unit): ForeverSelectorBuilder = - macro SelectorBuilder.timeoutImpl[Unit,ForeverSelectorBuilder] - - @inline - def timeoutWithFlowTerminationAsync(t:FiniteDuration, - f: (ExecutionContext, FlowTermination[Unit], FiniteDuration) => Future[Unit] ): this.type = - withTimeout(t){ sk => Some(f(ec,sk.flowTermination,t) map Function.const(sk)) } - - - def idle(body:Unit): ForeverSelectorBuilder = - macro SelectorBuilder.idleImpl[Unit,ForeverSelectorBuilder] - - def handleError(f: Throwable => Unit): ForeverSelectorBuilder = - macro SelectorBuilder.handleErrorImpl[Unit,ForeverSelectorBuilder] - - @inline - def handleErrorWithFlowTerminationAsync(f: (ExecutionContext, FlowTermination[Unit], Continuated[Unit], Throwable) => Future[Unit] ): this.type = - withError{ (ec,ft,cont,ex) => - f(ec,ft,cont,ex).map(Function.const(cont))(ec) - } - - - /** - * provide syntax for running select loop inside go (or async) block - * example of usage: - * - *{{{ - * go { - * ..... - * for(s <- gopherApi.select.forever) - * s match { - * case x: ch1.read => do something with x - * case q: chq.read => implicitly[FlowTermination[Unit]].doExit(()) - * case y: ch2.write if (y=expr) => do something with y - * case _ => do somethig when idle. - * } - *}}} - * - * Note, that you can use implicit instance of [FlowTermination[Unit]] to stop loop. - **/ - def foreach(f:Any=>Unit):Unit = - macro SelectorBuilderImpl.foreach[Unit] - - - - /** - * provide syntax for running select loop as async operation. - * - *{{{ - * val receiver = gopherApi.select.forever{ - * case x: channel.read => Console.println(s"received:\$x") - * } - *}}} - */ - def apply(f: PartialFunction[Any,Unit]): Future[Unit] = - macro SelectorBuilderImpl.apply[Unit] - - - def inputBuilder[B]() = new InputSelectorBuilder[B](api) - - /** - * provide syntax for creating output channels. - *{{{ - * - * val multiplexed = for(s <- gopherApi.select.forever) yield - * s match { - * case x: channelA => s"A:${x}" - * case x: channelB => s"B:${x}" - * } - * - *}}} - **/ - def map[B](f:Any=>B):Input[B] = macro SelectorBuilderImpl.map[B] - - def input[B](f:PartialFunction[Any,B]):Input[B] = - macro SelectorBuilderImpl.input[B] - -} - - - diff --git a/0.99.x/src/main/scala/gopher/channels/FutureInput.scala b/0.99.x/src/main/scala/gopher/channels/FutureInput.scala deleted file mode 100644 index 10c8e26f..00000000 --- a/0.99.x/src/main/scala/gopher/channels/FutureInput.scala +++ /dev/null @@ -1,65 +0,0 @@ -package gopher.channels - -import scala.concurrent._ -import scala.language.experimental.macros -import scala.reflect.macros.blackbox.Context -import scala.reflect.api._ -import scala.util._ -import java.util.concurrent.ConcurrentLinkedQueue -import gopher._ - -/** - * Future[A], represented as input which produce a value when completed, after this - * closes. If evaluation of feature is unsuccessful (i.e. failure), than appropriative - * exception is thrown during reading. - * - * - * Can be obtained from gopherApi. - * - *{{{ - * import gopherApi._ - * - * val myInput = futureInput(future) - * select.forever{ - * case x: myInput.read => Console.println(s"we receive value from future: \${x}") - * implicitly[FlowTermination[Unit]].doExit(()) - * case x: myChannel.read => Console.println(s"value from channel: \${x}") - * } - *}}} - * - * Also it is possiblt to direclty read from future in case guard: - *{{{ - * select.forever{ - * case x: T if (x==future.read) => Console.println(s"we receive value from future: \${x}") - * case x: T if (x==channel.read) => Console.println(s"value from channel: \${x}") - * } - *}}} - * - */ -class FutureInput[A](future: Future[A], override val api: GopherAPI) extends Input[A] -{ - - def cbread[B](f: ContRead[A,B] => Option[ContRead.In[A] => Future[Continuated[B]]], flwt: FlowTermination[B] ): Unit = - { - future.onComplete{ r => - for (f1 <- f(ContRead(f,this,flwt))) { - if (closed) - f1(ContRead.In.channelClosed) - else { - closed = true - r match { - case Success(x) => f1(ContRead.In value x) - case Failure(ex) => f1(ContRead.In failure ex) - } - } - } - }(api.gopherExecutionContext) - } - - def input: Input[A] = this - - @volatile private[this] var closed: Boolean = false - -} - - diff --git a/0.99.x/src/main/scala/gopher/channels/GopherAPIProvider.scala b/0.99.x/src/main/scala/gopher/channels/GopherAPIProvider.scala deleted file mode 100644 index 2e1d434c..00000000 --- a/0.99.x/src/main/scala/gopher/channels/GopherAPIProvider.scala +++ /dev/null @@ -1,8 +0,0 @@ -package gopher.channels - -import gopher._ - -trait GopherAPIProvider -{ - def api: GopherAPI -} diff --git a/0.99.x/src/main/scala/gopher/channels/GrowingBufferedChannel.scala b/0.99.x/src/main/scala/gopher/channels/GrowingBufferedChannel.scala deleted file mode 100644 index 0bff5042..00000000 --- a/0.99.x/src/main/scala/gopher/channels/GrowingBufferedChannel.scala +++ /dev/null @@ -1,79 +0,0 @@ -package gopher.channels - -import akka.actor._ -import scala.language._ -import scala.concurrent._ -import scala.collection.immutable._ -import gopher._ - -class ChannelOverflowException extends RuntimeException - -/** - * ChannelActor - actor, which leave - */ -class GrowingBufferedChannelActor[A](id:Long, limit:Int, api: GopherAPI) extends BaseBufferedChannelActor[A](id,api) -{ - - - protected[this] def onContWrite(cwa: gopher.channels.ContWrite[A, _]): Unit = - { - if (closed) { - cwa.flowTermination.throwIfNotCompleted(new ChannelClosedException()) - } else { - val prevNElements = nElements - if (processWriter(cwa) && prevNElements==0) { - processReaders() - } - } - } - - protected[this] def onContRead(cra: gopher.channels.ContRead[A, _]): Unit = - { - if (nElements==0) { - if (closed) { - processReaderClosed(cra) - } else { - readers = readers :+ cra - } - } else { - val prevNElements = nElements - if (processReader(cra)) { - if (closed) { - stopIfEmpty - } - } - } - } - - - protected[this] def processReader[B](reader:ContRead[A,B]): Boolean = - reader.function(reader) match { - case Some(f1) => - val readedElement = buffer.head.asInstanceOf[A] - buffer = buffer.tail - nElements-=1 - Future{ - val cont = f1(ContRead.In value readedElement ) - api.continue(cont, reader.flowTermination) - }(api.gopherExecutionContext) - true - case None => - false - } - - - private[this] def processWriter[B](writer:ContWrite[A,B]): Boolean = - writer.function(writer) match { - case Some((a,cont)) => - nElements+=1 - buffer = buffer :+ a - api.continue(cont, writer.flowTermination) - true - case None => - false - } - - - var buffer= Queue[Any]() - -} diff --git a/0.99.x/src/main/scala/gopher/channels/Input.scala b/0.99.x/src/main/scala/gopher/channels/Input.scala deleted file mode 100644 index 487c550b..00000000 --- a/0.99.x/src/main/scala/gopher/channels/Input.scala +++ /dev/null @@ -1,591 +0,0 @@ -package gopher.channels - -import scala.concurrent._ -import scala.concurrent.duration._ -import scala.language.experimental.macros -import scala.language.reflectiveCalls -import scala.reflect.macros.blackbox.Context -import gopher._ -import gopher.util._ -import java.util.concurrent.atomic._ - -import gopher.channels.ContRead.{ChannelClosed, In} - -/** - * Entity, from which we can read objects of type A. - * - * - */ -trait Input[A] extends GopherAPIProvider -{ - - thisInput => - - type <~ = A - type read = A - - case class Read(value:A) - - /** - * apply f, when input will be ready and send result to API processor - */ - def cbread[B](f: - ContRead[A,B]=>Option[ - ContRead.In[A]=>Future[Continuated[B]] - ], - ft: FlowTermination[B]): Unit - - - /** - * async version of read. Immediatly return future, which will contains result of read or failur with StreamClosedException - * in case of stream is closed. - */ - def aread:Future[A] = { - val ft = PromiseFlowTermination[A]() - cbread[A](cont => Some(ContRead.liftIn(cont) { - a => Future.successful(Done(a,ft)) - }), ft) - ft.future - } - - def timedAread(waitTime:FiniteDuration): Future[A] = { - val ft = PromiseFlowTermination[A]() - cbread[A]( - {cont => if (ft.isCompleted) None - else Some(ContRead.liftIn(cont) { - a => Future successful Done(a,ft) - }) - }, ft) - implicit val ec = api.gopherExecutionContext - api.actorSystem.scheduler.scheduleOnce(waitTime){ - ft.throwIfNotCompleted(new TimeoutException) - } - ft.future - } - - /** - * instance of gopher API - */ - def api: GopherAPI - - /** - * read object from channel. Must be situated inside async/go/action block. - */ - def read:A = macro InputMacro.read[A] - - /** - * synonym for read. - */ - def ? : A = macro InputMacro.read[A] - - /** - * return feature which contains sequence from first `n` elements. - */ - def atake(n:Int):Future[IndexedSeq[A]] = - { - if (n==0) { - Future successful IndexedSeq() - } else { - val ft = PromiseFlowTermination[IndexedSeq[A]] - @volatile var i = 0; - @volatile var r: IndexedSeq[A] = IndexedSeq() - def takeFun(cont:ContRead[A,IndexedSeq[A]]):Option[ContRead.In[A]=>Future[Continuated[IndexedSeq[A]]]] = - Some{ - ContRead.liftIn(cont) { a => - i += 1 - r = r :+ a - if (i f each time when new object is arrived. Ended when input closes. - * - * must be inside go/async/action block. - */ - def foreach(f: A=>Unit): Unit = macro InputMacro.foreachImpl[A] - - def aforeach(f: A=>Unit): Future[Unit] = macro InputMacro.aforeachImpl[A] - - class Filtered(p: A=>Boolean) extends Input[A] { - - def cbread[B](f:ContRead[A,B]=>Option[ContRead.In[A]=>Future[Continuated[B]]], ft: FlowTermination[B]): Unit = - thisInput.cbread[B]({ cont => - f(cont) map { f1 => - { case v@ContRead.Value(a) => - if (p(a)) { - f1(v) - } else { - f1(ContRead.Skip) - Future successful cont - } - case v@_ => f1(v) - } - } }, ft) - - def api = thisInput.api - - } - - def filter(p: A=>Boolean): Input[A] = new Filtered(p) - - def withFilter(p: A=>Boolean): Input[A] = filter(p) - - class Mapped[B](g: A=>B) extends Input[B] - { - - def cbread[C](f: ContRead[B,C] => Option[ContRead.In[B]=>Future[Continuated[C]]], ft: FlowTermination[C] ): Unit = - { - def mf(cont:ContRead[A,C]):Option[ContRead.In[A]=>Future[Continuated[C]]] = - { val contA = ContRead(f,this,cont.flowTermination) - f(contA) map (f1 => { case v@ContRead.Value(a) => f1(ContRead.Value(g(a))) - case ContRead.Skip => f1(ContRead.Skip) - Future successful cont - case ChannelClosed => f1(ChannelClosed) - case ContRead.Failure(ex) => f1(ContRead.Failure(ex)) - } ) - } - thisInput.cbread(mf,ft) - } - - def api = thisInput.api - - } - - def map[B](g: A=>B): Input[B] = new Mapped(g) - - class FutureMapped[B](g: A=>Future[B]) extends Input[B] - { - - override def cbread[C](f: (ContRead[B, C]) => Option[(ContRead.In[B]) => Future[Continuated[C]]], ft: FlowTermination[C]): Unit = { - def mf(cont:ContRead[A,C]):Option[ContRead.In[A]=>Future[Continuated[C]]] = - { val contA = ContRead(f,this,cont.flowTermination) - implicit val ec = api.gopherExecutionContext - f(contA) map (f1 => { - case v@ContRead.Value(a) => g(a).flatMap(x => f1(ContRead.Value(x))) - case ContRead.Skip => f1(ContRead.Skip) - Future successful cont - case ChannelClosed => f1(ChannelClosed) - case ContRead.Failure(ex) => f1(ContRead.Failure(ex)) - } ) - } - thisInput.cbread(mf,ft) - } - - override def api: GopherAPI = thisInput.api - } - - def amap[B](g: A=>Future[B]):Input[B] = new FutureMapped(g) - - def zip[B](x: Iterable[B]): Input[(A,B)] = zip(Input.asInput(x,api)) - - def zip[B](x: Input[B]): Input[(A,B)] = new ZippedInput(api,this,x) - - def flatMapOp[B](g: A=>Input[B])(op:(Input[B],Input[B])=>Input[B]):Input[B] = new Input[B] { - - def cbread[C](f: ContRead[B,C] => Option[ContRead.In[B]=>Future[Continuated[C]]], ft: FlowTermination[C] ): Unit = - { - def mf(cont:ContRead[A,C]):Option[ContRead.In[A]=>Future[Continuated[C]]] = - { val contA = ContRead(f,this,cont.flowTermination) - f(contA) map { f1 => { - case v@ContRead.Value(a) => Future successful ContRead(f,op(g(a),this),cont.flowTermination) - case ContRead.Skip => f1(ContRead.Skip) - Future successful cont - case ChannelClosed => f1(ChannelClosed) - case ContRead.Failure(ex) => f1(ContRead.Failure(ex)) - }}} - thisInput.cbread(mf,ft) - } - - def api = thisInput.api - } - - def flatMap[B](g: A=>Input[B]):Input[B] = flatMapOp(g)( _ or _) - - def seq = new { - def flatMap[B](g: A=>Input[B]):Input[B] = flatMapOp(g)( _ append _ ) - } - - /** - * return input merged with 'other'. - * (i.e. non-determenistics choice) - **/ - def |(other:Input[A]):Input[A] = new OrInput(this,other) - - /** - * synonim for non-deteremenistics choice. - **/ - def or(other:Input[A]):Input[A] = new OrInput(this,other) - - /** - * when the first channel is exhaused, read from second. - **/ - def append(other:Input[A]):Input[A] = new Input[A] { - - def cbread[C](f: ContRead[A,C] => Option[ContRead.In[A]=>Future[Continuated[C]]], ft: FlowTermination[C] ): Unit = - { - def mf(cont:ContRead[A,C]):Option[ContRead.In[A]=>Future[Continuated[C]]] = - { val contA = ContRead(f,this,cont.flowTermination) - f(contA) map (f1 => { case v@ContRead.Value(a) => f1(ContRead.Value(a)) - case ContRead.Skip => f1(ContRead.Skip) - Future successful cont - case ChannelClosed => f1(ContRead.Skip) - Future successful ContRead(f,other,cont.flowTermination) - case ContRead.Failure(ex) => f1(ContRead.Failure(ex)) - }) - } - thisInput.cbread(mf,ft) - } - - def api = thisInput.api - - } - - def prepend(a:A):Input[A] = new Input[A] { - - val aReaded = new AtomicBoolean(false) - - def cbread[C](f: ContRead[A,C] => Option[ContRead.In[A]=>Future[Continuated[C]]], ft: FlowTermination[C] ): Unit = - { - f(ContRead(f,this,ft)) map { f1 => - if (aReaded.compareAndSet(false,true)) { - f1(ContRead.Value(a)) - } else { - api.continuatedProcessorRef ! ContRead(f,thisInput,ft) - f1(ContRead.Skip) - } - } - } - - def api = thisInput.api - - } - - - /** - * return pair of inputs `(ready, timeouts)`, such that when you read from `ready` you receive element from `this` - * and if during reading you wait more than specified `timeout`, than timeout message is appear in `timeouts` - * - *``` - * val (inReady, inTimeouts) = in withInputTimeouts (10 seconds) - * select.forever { - * case x: inReady.read => Console.println(s"received value \${value}") - * case x: inTimeouts.read => Console.println(s"timeout occured") - * } - *``` - **/ - def withInputTimeouts(timeout: FiniteDuration): (Input[A],Input[FiniteDuration]) = - new InputWithTimeouts(this,timeout).pair - - /** - * duplicate input - */ - def dup(): (Input[A],Input[A]) = - (new DuppedInput(this)).pair - - def async = new { - - def foreach(f: A=> Unit):Future[Unit] = macro InputMacro.aforeachImpl[A] - - @inline - def foreachSync(f: A=>Unit): Future[Unit] = thisInput.foreachSync(f) - - @inline - def foreachAsync(f: A=>Future[Unit])(implicit ec:ExecutionContext): Future[Unit] = - thisInput.foreachAsync(f)(ec) - - } - - def foreachSync(f: A=>Unit): Future[Unit] = - { - val ft = PromiseFlowTermination[Unit] - lazy val contForeach = ContRead(applyF,this,ft) - def applyF(cont:ContRead[A,Unit]):Option[ContRead.In[A]=>Future[Continuated[Unit]]] = - Some( (in:ContRead.In[A]) => - in match { - case ChannelClosed => Future successful Done((),ft) - case x => ContRead.liftIn(cont){ x => f(x) - Future successful contForeach - }(x) - } - ) - cbread(applyF, ft) - ft.future - } - - def foreachAsync(f: A=>Future[Unit])(implicit ec:ExecutionContext): Future[Unit] = - { - val ft = PromiseFlowTermination[Unit] - def applyF(cont:ContRead[A,Unit]):Option[ContRead.In[A]=>Future[Continuated[Unit]]] = - Some{ - case ChannelClosed => Future successful Done((),ft) - case in => - ContRead.liftIn(cont){ x => f(x) map ( _ => ContRead(applyF, this, ft) ) }(in) - } - cbread(applyF,ft) - ft.future - } - - def flatFold(fun:(Input[A],A)=>Input[A]):Input[A] = new Input[A] { - - val current = new AtomicReference[Input[A]](thisInput) - - def cbread[C](f: ContRead[A,C] => Option[ContRead.In[A]=>Future[Continuated[C]]], ft: FlowTermination[C] ): Unit = - { - def mf(cont:ContRead[A,C]):Option[ContRead.In[A]=>Future[Continuated[C]]] = - f(ContRead(f,this,ft)) map { next => - { case ContRead.Value(a) => - var changed = false - while(!changed) { - var prev = current.get - var next = fun(prev,a) - changed = current.compareAndSet(prev,next) - } - next(ContRead.Value(a)) - // fp-version. - // next(ContRead.Skip) - //ContRead(f, one(a) append (fun(this,a) flatFold fun),ft) - case v@_ => next(v) - } } - current.get.cbread(mf,ft) - } - - def api = thisInput.api - - } - - /** - * async incarnation of fold. Fold return future, which successed when channel is closed. - *Operations withing fold applyed on result on each other, starting with s0. - *``` - * val fsum = ch.afold(0){ (s, n) => s+n } - *``` - * Here in fsum will be future with value: sum of all elements in channel until one has been closed. - **/ - def afold[S,B](s0:S)(f:(S,A)=>S): Future[S] = macro InputMacro.afoldImpl[A,S] - - /** - * fold opeations, available inside async bloc. - *``` - * go { - * val sum = ch.fold(0){ (s,n) => s+n } - * } - *``` - */ - def fold[S,B](s0:S)(f:(S,A)=>S): S = macro InputMacro.foldImpl[A,S] - - - - def afoldSync[S,B](s0:S)(f:(S,A)=>S): Future[S] = - { - val ft = PromiseFlowTermination[S] - var s = s0 - def applyF(cont:ContRead[A,S]):Option[ContRead.In[A]=>Future[Continuated[S]]] = - { - val contFold = ContRead(applyF,this,ft) - Some{ - case ChannelClosed => Future successful Done(s,ft) - case ContRead.Value(a) => s = f(s,a) - Future successful contFold - case ContRead.Skip => Future successful contFold - case ContRead.Failure(ex) => Future failed ex - } - } - cbread(applyF,ft) - ft.future - } - - def afoldAsync[S,B](s0:S)(f:(S,A)=>Future[S])(implicit ec:ExecutionContext): Future[S] = - { - val ft = PromiseFlowTermination[S] - var s = s0 - def applyF(cont:ContRead[A,S]):Option[ContRead.In[A]=>Future[Continuated[S]]] = - { - Some{ - case ChannelClosed => Future successful Done(s,ft) - case ContRead.Value(a) => f(s,a) map { x => - s = x - ContRead(applyF,this,ft) - } - case ContRead.Skip => Future successful ContRead(applyF,this,ft) - case ContRead.Failure(ex) => Future failed ex - } - } - cbread(applyF,ft) - ft.future - } - - - /** - * @return Input without close event: i.e. reading from closeless channel - * after channel close will wait forever instead throwing CloseChannelException - */ - lazy val closeless : Input[A] = new Input[A]() { - - - override def cbread[B](f: (ContRead[A, B]) => Option[(In[A]) => Future[Continuated[B]]], ft: FlowTermination[B]): Unit = { - implicit val es:ExecutionContext = api.gopherExecutionContext - def fc(cont:ContRead[A,B]):Option[(In[A])=>Future[Continuated[B]]] = - { - f(ContRead(f,this,cont.flowTermination)) map { - g => { - case ContRead.ChannelClosed => g(ContRead.Skip) map (_ => Never) - case x => g(x) - } - } - } - thisInput.cbread(fc,ft) - } - - override lazy val closeless: Input[A] = this - - override def api: GopherAPI = thisInput.api - - } - - - -} - -object Input -{ - def asInput[A](iterable:Iterable[A], api: GopherAPI): Input[A] = new IterableInput(iterable.iterator, api) - - class IterableInput[A](it: Iterator[A], override val api: GopherAPI) extends Input[A] - { - - def cbread[B](f:ContRead[A,B]=>Option[ContRead.In[A]=>Future[Continuated[B]]], ft: FlowTermination[B]): Unit = - f(ContRead(f,this,ft)) map (f1 => { val next = this.synchronized { - if (it.hasNext) - ContRead.Value(it.next) - else - ChannelClosed - } - api.continue(f1(next),ft) - } - ) - } - - def closed[A](implicit gopherApi: GopherAPI): Input[A] = new Input[A] { - - def cbread[B](f:ContRead[A,B]=>Option[ContRead.In[A]=>Future[Continuated[B]]], ft: FlowTermination[B]): Unit = - f(ContRead(f,this,ft)) map (f1 => f1(ChannelClosed)) - - def api = gopherApi - } - - def one[A](a:A)(implicit gopherApi: GopherAPI): Input[A] = new Input[A] { - - val readed: AtomicBoolean = new AtomicBoolean(false) - - def cbread[B](f:ContRead[A,B]=>Option[ContRead.In[A]=>Future[Continuated[B]]], ft: FlowTermination[B]): Unit = - f(ContRead(f,this,ft)) map (f1 => f1( - if (readed.compareAndSet(false,true)) { - ContRead.Value(a) - }else{ - ChannelClosed - })) - - def api = gopherApi - } - - - def zero[A](implicit gopherAPI: GopherAPI):Input[A] = new Input[A] { - - /** - * will eat f without a trace (i.e. f will be never called) - */ - override def cbread[B](f: (ContRead[A, B]) => Option[(In[A]) => Future[Continuated[B]]], ft: FlowTermination[B]): Unit = {} - - /** - * instance of gopher API - */ - override def api: GopherAPI = gopherAPI - } - - def always[A](a:A)(implicit gopherApi: GopherAPI):Input[A] = new Input[A] { - - override def api = gopherApi - - /** - * apply f, when input will be ready and send result to API processor - */ - override def cbread[B](f: (ContRead[A, B]) => Option[(In[A]) => Future[Continuated[B]]], ft: FlowTermination[B]) = - Future{ - f(ContRead(f,this,ft)) foreach { - g => g(ContRead.In.value(a)) - } - }(api.gopherExecutionContext) - - } - -} - - - -object InputMacro -{ - - def read[A](c:Context):c.Expr[A] = - { - import c.universe._ - c.Expr[A](q"{scala.async.Async.await(${c.prefix}.aread)}") - } - - def foreachImpl[A](c:Context)(f:c.Expr[A=>Unit]): c.Expr[Unit] = - { - import c.universe._ - c.Expr[Unit](q"scala.async.Async.await(${aforeachImpl(c)(f)})") - } - - - def aforeachImpl[A](c:Context)(f:c.Expr[A=>Unit]): c.Expr[Future[Unit]] = - { - import c.universe._ - f.tree match { - case Function(valdefs,body) => - if (MacroUtil.hasAwait(c)(body)) { - // TODO: add support for flow-termination (?) - val nbody = q"scala.async.Async.async(${body})" - val nfunction = atPos(f.tree.pos)(Function(valdefs,nbody)) - val ntree = q"${c.prefix}.foreachAsync(${nfunction})" - c.Expr[Future[Unit]](c.untypecheck(ntree)) - } else { - c.Expr[Future[Unit]](q"${c.prefix}.foreachSync(${f.tree})") - } - case _ => c.abort(c.enclosingPosition,"function expected") - } - } - - def foldImpl[A,S](c:Context)(s0:c.Expr[S])(f:c.Expr[(S,A)=>S]): c.Expr[S] = - { - import c.universe._ - c.Expr[S](q"scala.async.Async.await(${afoldImpl(c)(s0)(f)})") - } - - def afoldImpl[A,S](c:Context)(s0:c.Expr[S])(f:c.Expr[(S,A)=>S]): c.Expr[Future[S]] = - { - import c.universe._ - f.tree match { - case Function(valdefs,body) => - if (MacroUtil.hasAwait(c)(body)) { - val nbody = atPos(body.pos)(q"scala.async.Async.async(${body})") - val nfunction = atPos(f.tree.pos)(Function(valdefs,nbody)) - val ntree = q"${c.prefix}.afoldAsync(${s0.tree})(${nfunction})" - c.Expr[Future[S]](c.untypecheck(ntree)) - } else { - c.Expr[Future[S]](q"${c.prefix}.afoldSync(${s0.tree})(${f.tree})") - } - } - } - - -} diff --git a/0.99.x/src/main/scala/gopher/channels/InputOutput.scala b/0.99.x/src/main/scala/gopher/channels/InputOutput.scala deleted file mode 100644 index 1b77e4cd..00000000 --- a/0.99.x/src/main/scala/gopher/channels/InputOutput.scala +++ /dev/null @@ -1,155 +0,0 @@ -package gopher.channels - -import java.util.concurrent.ConcurrentHashMap -import java.util.concurrent.atomic.AtomicLong - -import gopher.{ChannelClosedException, FlowTermination, GopherAPI} - -import scala.concurrent.Future -import scala.util.{Failure, Success} - -trait InputOutput[A,B] extends Input[B] with Output[A] -{ - - thisInputOutput => - - trait CbwriteDelegate extends Output[A] - { - override def cbwrite[B](f: (ContWrite[A, B]) => Option[(A, Future[Continuated[B]])], ft: FlowTermination[B]): Unit = thisInputOutput.cbwrite(f,ft) - - } - - class MappedIO[C](g: B=>C) extends Mapped[C](g) - with CbwriteDelegate with InputOutput[A,C] - - override def map[C](g:B=>C): InputOutput[A,C] = new MappedIO(g) - - class FilteredIO(p: B=>Boolean) extends Filtered(p) - with CbwriteDelegate with InputOutput[A,B] - - override def filter(p: B=>Boolean):InputOutput[A,B] = new FilteredIO(p) - - - - class CompositionIO[C](other:InputOutput[B,C]) extends InputOutput[A,C] - { - - protected val internalFlowTermination = PromiseFlowTermination[Unit]() - - private val listeners = new ConcurrentHashMap[Long,ContRead.AuxE[C]] - private val listenerIdsGen = new AtomicLong(0L) - override val api: GopherAPI = thisInputOutput.api - - - - def internalRead(cr:ContRead[B,Unit]):Option[(ContRead.In[B]=>Future[Continuated[Unit]])] = { - { - implicit val ec = api.gopherExecutionContext - Some { - case ContRead.Value(a) => - other.cbwrite[Unit](cw => { - Some { - (a, Future successful cr) - } - }, internalFlowTermination) - Future successful Never // cr will be called after write. - case ContRead.Skip => Future successful cr - case ContRead.ChannelClosed => - internalFlowTermination.doExit(()) - Future successful Never - case ContRead.Failure(ex) => internalFlowTermination.throwIfNotCompleted(ex) - Future successful Never - } - } - } - - thisInputOutput.cbread(internalRead,internalFlowTermination) - - internalFlowTermination.future.onComplete{ r => - listeners.forEach { (id, cr) => - val cre = cr.asInstanceOf[ContRead[A,cr.S]] - cre.function(cre).foreach{ q => - val n = r match { - case Failure(ex) => ContRead.Failure(ex) - case Success(_) => ContRead.ChannelClosed - } - api.continue(q(n),cre.flowTermination) - } - } - }(api.gopherExecutionContext) - - override def cbread[D](f: (ContRead[C, D]) => Option[ContRead.In[C] => Future[Continuated[D]]], ft: FlowTermination[D]): Unit = - { - val id = listenerIdsGen.incrementAndGet() - def wf(cr:ContRead[C,D]):Option[ContRead.In[C] => Future[Continuated[D]]]= - { - f(cr) map { q => - listeners.remove(id) - q - } - } - val cr = ContRead(f,this,ft) - listeners.put(id,cr.asInstanceOf[ContRead.AuxE[C]]) - other.cbread(wf,ft) - } - - override def cbwrite[B](f: (ContWrite[A, B]) => Option[(A, Future[Continuated[B]])], ft: FlowTermination[B]): Unit = - { - - if (checkNotCompleted(ft)) thisInputOutput.cbwrite(f,ft) - } - - private def checkNotCompleted[D](ft:FlowTermination[D]): Boolean = - { - if (internalFlowTermination.isCompleted) { - implicit val ec = api.gopherExecutionContext - internalFlowTermination.future.onComplete{ - case Failure(ex) => ft.doThrow(ex) - case Success(_) => ft.doThrow(new ChannelClosedException()) - } - false - } else true - } - - } - - def compose[C](other:InputOutput[B,C]):InputOutput[A,C] = - new CompositionIO[C](other) - - /** - * Synonym for this.compose(other) - */ - def |>[C](other:InputOutput[B,C]):InputOutput[A,C] = this.compose(other) - - -} - - -trait CloseableInputOutput[A,B] extends InputOutput[A,B] with CloseableInput[B] -{ - - thisCloseableInputOutput => - - class MappedIOC[C](g: B=>C) extends MappedIO[C](g) with DoneSignalDelegate[C] with CloseableInputOutput[A,C] - - override def map[C](g: B=>C): CloseableInputOutput[A,C] = new MappedIOC(g) - - class FilteredIOC(p: B=>Boolean) extends FilteredIO(p) - with DoneSignalDelegate[B] - with CloseableInputOutput[A,B] - - override def filter(p: B=>Boolean): CloseableInputOutput[A,B] = new FilteredIOC(p) - - class CompositionIOC[C](other: InputOutput[B,C]) extends CompositionIO(other) - with DoneSignalDelegate[C] - with CloseableInputOutput[A,C] - - override def compose[C](other: InputOutput[B,C]):CloseableInputOutput[A,C] = - new CompositionIOC[C](other) { - - } - - override def |>[C](other:InputOutput[B,C]): CloseableInputOutput[A,C] = this.compose(other) - - -} \ No newline at end of file diff --git a/0.99.x/src/main/scala/gopher/channels/InputSelectorBuilder.scala b/0.99.x/src/main/scala/gopher/channels/InputSelectorBuilder.scala deleted file mode 100644 index 7c84b281..00000000 --- a/0.99.x/src/main/scala/gopher/channels/InputSelectorBuilder.scala +++ /dev/null @@ -1,119 +0,0 @@ -package gopher.channels - -import java.util.concurrent.atomic.AtomicBoolean - -import scala.language.experimental.macros -import scala.reflect.macros.whitebox.Context -import scala.reflect.api._ -import gopher._ -import gopher.util._ - -import scala.concurrent._ -import scala.concurrent.duration._ -import scala.annotation.unchecked._ - - -/** - * Builder for 'input' selector. Can be obtained as `gopherApi.select.input`. - * or map of forever selector. - * - * - */ -class InputSelectorBuilder[T](override val api: GopherAPI) extends SelectorBuilder[T@uncheckedVariance] - with CloseableInput[T] -{ - - val proxy = api.makeChannel[T]() - val terminated = new AtomicBoolean(false) - - def reading[A](ch: Input[A])(f: A=>T): InputSelectorBuilder[T] = - macro SelectorBuilder.readingImpl[A,T,InputSelectorBuilder[T]] - - @inline - def readingWithFlowTerminationAsync[A](ch: Input[A], f: (ExecutionContext, FlowTermination[T], A) => Future[T] ): InputSelectorBuilder[T] = - { - def normalized(_cont:ContRead[A,T]):Option[ContRead.In[A]=>Future[Continuated[T]]] = - Some(ContRead.liftIn(_cont)(a=>f(ec,selector,a) flatMap { x => - if (!terminated.get) { - proxy.awrite(x) - } else Future successful x - } map Function.const(ContRead(normalized,ch,selector)))) - withReader[A](ch,normalized) - } - - /** - * write x to channel if possible - */ - def writing[A](ch: Output[A], x: A)(f: A=>T): InputSelectorBuilder[T] = - macro SelectorBuilder.writingImpl[A,T,InputSelectorBuilder[T]] - - @inline - def writingWithFlowTerminationAsync[A](ch:Output[A], x: =>A, f: (ExecutionContext, FlowTermination[T], A) => Future[T] ): this.type = - withWriter[A](ch, { cw => Some(x,f(ec,cw.flowTermination,x) flatMap { - x=>proxy.awrite(x) - } map Function.const(cw)) }) - -/* - def idle(body: T): InputSelectorBuilder[T] = - macro SelectorBuilder.idleImpl[T,InputSelectorBuilder[T]] - - @inline - def idleWithFlowTerminationAsync(f: (ExecutionContext, FlowTermination[T]) => Future[T] ): this.type = - withIdle{ sk => Some(f(ec,sk.flowTermination) flatMap(x => - proxy.awrite(x)) map(Function.const(sk)) ) } -*/ - - def timeout(t:FiniteDuration)(f: FiniteDuration => T): InputSelectorBuilder[T] = - macro SelectorBuilder.timeoutImpl[T,InputSelectorBuilder[T]] - - @inline - def timeoutWithFlowTerminationAsync(t:FiniteDuration, - f: (ExecutionContext, FlowTermination[T], FiniteDuration) => Future[T] ): this.type = - withTimeout(t){ sk => Some(f(ec,sk.flowTermination,t) flatMap( x => - proxy.awrite(x)) map(Function.const(sk)) ) } - - def handleError(f: Throwable => T): InputSelectorBuilder[T] = - macro SelectorBuilder.handleErrorImpl[T,InputSelectorBuilder[T]] - - @inline - def handleErrorWithFlowTerminationAsync(f: (ExecutionContext, FlowTermination[T], Continuated[T], Throwable) => Future[T] ): this.type = - withError{ (ec,ft,cont,ex) => - f(ec,ft,cont,ex).flatMap(x=> proxy.awrite(x).map(Function.const(cont))(ec))(ec) - } - - - def foreach(f:Any=>T):T = - macro SelectorBuilderImpl.foreach[T] - - def apply(f: PartialFunction[Any,T]): Future[T] = - macro SelectorBuilderImpl.apply[T] - - // input methods - def cbread[B](f: - ContRead[T,B]=>Option[ - ContRead.In[T]=>Future[Continuated[B]] - ], - ft: FlowTermination[B]): Unit = proxy.cbread(f,ft) - - val done: Input[Unit] = proxy.done - - def started: InputSelectorBuilder[T] = { go; this } - - // - override val selector = new Selector[T](api) { - override def doExit(a: T): T = - { - if (terminated.compareAndSet(false,true)) { - proxy.awrite(a) onComplete { - _ => proxy.close() - } - //proxy.close() - } - super.doExit(a) - } - - } - -} - - diff --git a/0.99.x/src/main/scala/gopher/channels/InputWithTimeouts.scala b/0.99.x/src/main/scala/gopher/channels/InputWithTimeouts.scala deleted file mode 100644 index 4c1f7762..00000000 --- a/0.99.x/src/main/scala/gopher/channels/InputWithTimeouts.scala +++ /dev/null @@ -1,52 +0,0 @@ -package gopher.channels - -import scala.concurrent._ -import scala.concurrent.duration._ -import scala.util._ -import gopher._ - - -/** - * Wrap `origin` input into input, which produce 'timeout' value into `timeouts` channel - * when reading from wrapped channel take more time than `timeout` . - * - *@see InputChannel.withInputTimeouts - */ -class InputWithTimeouts[A](origin: Input[A], timeout: FiniteDuration) -{ - - def pair: (Input[A],Input[FiniteDuration]) = (wrapped, timeouts) - - val timeouts = origin.api.makeChannel[FiniteDuration]() - - val wrapped = new Input[A] { - - def cbread[B](f: ContRead[A,B] => Option[ContRead.In[A]=>Future[Continuated[B]]], ft: FlowTermination[B]): Unit = - { - val c = api.actorSystem.scheduler.scheduleOnce(timeout){ - timeouts.awrite(timeout) - }(api.gopherExecutionContext) - def fIn(cont: ContRead[A,B]):Option[ContRead.In[A]=>Future[Continuated[B]]] = - { - f(ContRead(f,this,ft)) map { f1 => - c.cancel() - in => in match { - case ContRead.Skip => Future successful ContRead(f,this,ft) - case ContRead.ChannelClosed => - timeouts.close() - f1(ContRead.ChannelClosed) - case x@_ => f1(x) - } - } - } - - origin.cbread(fIn,ft) - } - - def api = origin.api - - } - - -} - diff --git a/0.99.x/src/main/scala/gopher/channels/LazyChannel.scala b/0.99.x/src/main/scala/gopher/channels/LazyChannel.scala deleted file mode 100644 index 76100ff8..00000000 --- a/0.99.x/src/main/scala/gopher/channels/LazyChannel.scala +++ /dev/null @@ -1,24 +0,0 @@ -package gopher.channels - - -import scala.concurrent._ -import scala.concurrent.duration._ -import gopher._ - -/** - * lazy channel, which created during first input/output operations. - * (used in transputers as default value for In/Out Ports) - */ -class LazyChannel[A](override val api: GopherAPI) extends Input[A] with Output[A] -{ - - def cbread[B](f: ContRead[A,B] => Option[ContRead.In[A] => Future[Continuated[B]]], flwt: FlowTermination[B] ): Unit = - origin.cbread(f,flwt) - - def cbwrite[B](f: ContWrite[A,B] => Option[(A,Future[Continuated[B]])], flwt: FlowTermination[B] ): Unit = - origin.cbwrite(f,flwt) - - lazy val origin = api.makeChannel[A]() - -} - diff --git a/0.99.x/src/main/scala/gopher/channels/OnceSelectorBuilder.scala b/0.99.x/src/main/scala/gopher/channels/OnceSelectorBuilder.scala deleted file mode 100644 index 09c08ee9..00000000 --- a/0.99.x/src/main/scala/gopher/channels/OnceSelectorBuilder.scala +++ /dev/null @@ -1,87 +0,0 @@ -package gopher.channels - -import scala.language.experimental.macros -import scala.reflect.macros.whitebox.Context -import scala.reflect.api._ -import gopher._ -import gopher.util._ -import scala.concurrent._ -import scala.concurrent.duration._ -import scala.annotation.unchecked._ - - -/** - * Builder for 'once' selector. Can be obtained as `gopherApi.select.once`. - */ -trait OnceSelectorBuilder[T] extends SelectorBuilder[T@uncheckedVariance] -{ - - private var done = false - - def reading[A](ch: Input[A])(f: A=>T): OnceSelectorBuilder[T] = - macro SelectorBuilder.readingImpl[A,T,OnceSelectorBuilder[T]] - - @inline - def readingWithFlowTerminationAsync[A](ch: Input[A], f: (ExecutionContext, FlowTermination[T], A) => Future[T] ): OnceSelectorBuilder[T] = - withReader[A](ch, { - cr => if (done) None - else Some(ContRead.liftIn(cr)(a => - f(ec,cr.flowTermination,a) map { makeDone(_,cr.flowTermination) })) - } ) - - /** - * write x to channel if possible - */ - def writing[A](ch: Output[A], x: A)(f: A=>T): OnceSelectorBuilder[T] = - macro SelectorBuilder.writingImpl[A,T,OnceSelectorBuilder[T]] - - @inline - def writingWithFlowTerminationAsync[A](ch:Output[A], x: =>A, f: (ExecutionContext, FlowTermination[T], A) => Future[T] ): this.type = - withWriter[A](ch, { cw => - if (done) None - else Some(x,f(ec,cw.flowTermination,x) map{ makeDone(_,cw.flowTermination)} ) - } ) - - def timeout(t:FiniteDuration)(f: FiniteDuration => T): OnceSelectorBuilder[T] = - macro SelectorBuilder.timeoutImpl[T,OnceSelectorBuilder[T]] - - - @inline - def timeoutWithFlowTerminationAsync(t:FiniteDuration, - f: (ExecutionContext, FlowTermination[T], FiniteDuration) => Future[T] ): this.type = - withTimeout(t){ sk => - if (done) None - else Some(f(ec,sk.flowTermination,t).map{ x => makeDone(x,sk.flowTermination)} ) - } - - - def idle(body: T): OnceSelectorBuilder[T] = - macro SelectorBuilder.idleImpl[T,OnceSelectorBuilder[T]] - - - def handleError(f: Throwable => T): OnceSelectorBuilder[T] = - macro SelectorBuilder.handleErrorImpl[T,OnceSelectorBuilder[T]] - - @inline - def handleErrorWithFlowTerminationAsync(f: (ExecutionContext, FlowTermination[T], Continuated[T], Throwable) => Future[T] ): this.type = - withError{ (ec,ft,cont,ex) => - f(ec,ft,cont,ex).map(x=>Done(x,ft))(ec) - } - - @inline - def makeDone(v:T, ft:FlowTermination[T]):Done[T] = - { - done = true - Done(v,ft) - } - - - def foreach(f:Any=>T):T = - macro SelectorBuilderImpl.foreach[T] - - def apply(f: PartialFunction[Any,T]): Future[T] = - macro SelectorBuilderImpl.apply[T] - -} - - diff --git a/0.99.x/src/main/scala/gopher/channels/OneTimeChannel.scala b/0.99.x/src/main/scala/gopher/channels/OneTimeChannel.scala deleted file mode 100644 index 79054564..00000000 --- a/0.99.x/src/main/scala/gopher/channels/OneTimeChannel.scala +++ /dev/null @@ -1,76 +0,0 @@ -package gopher.channels - -import scala.concurrent.{Channel=>_,Promise,Future} -import gopher._ -import java.util.concurrent.atomic._ - -import gopher.channels.ContRead.In - -/** - * channel, in which only one message can be written, - * after which it is automatically closed - * - * Writer is not waiting for reader to start. - */ -class OneTimeChannel[T](override val api:GopherAPI) extends Channel[T] -{ - - thisOneTimeChannel => - - private[this] val p = Promise[T]() - private[this] val readed = new AtomicBoolean(false) - - def future = p.future - def promise = p - - def cbread[B](f: ContRead[T,B] => Option[ContRead.In[T] => Future[Continuated[B]]],ft: FlowTermination[B]): Unit = - { - p.future.foreach{ a => - f(ContRead(f,this,ft)) foreach { g => - if (readed.compareAndSet(false,true)) { - api.continue(g(ContRead.Value(a)),ft) - } else{ - api.continue(g(ContRead.Skip),ft) - } - } - }(api.gopherExecutionContext) - } - - def cbwrite[B](f: ContWrite[T,B] => Option[(T, Future[Continuated[B]])],ft: FlowTermination[B]): Unit = - { - if (p.isCompleted) { - ft.doThrow(new ChannelClosedException()) - } else { - f(ContWrite(f,this,ft)) foreach { case (a, next) => - if (!p.trySuccess(a)) { - ft.doThrow(throw new ChannelClosedException()) - } - api.continue(next,ft) - } - } - } - - - def close(): Unit = - p failure new ChannelClosedException() - - val done = new Input[Unit] { - - override def cbread[B](f: (ContRead[Unit, B]) => Option[(In[Unit]) => Future[Continuated[B]]], ft: FlowTermination[B]): Unit = - { - val cr = ContRead[Unit,B](f,this,ft) - p.future.onComplete{ _ => applyDone(cr) }(api.gopherExecutionContext) - } - - override def api: GopherAPI = thisOneTimeChannel.api - } - -} - -object OneTimeChannel -{ - - def apply[A]()(implicit api:GopherAPI): OneTimeChannel[A] = - new OneTimeChannel[A](api) - -} diff --git a/0.99.x/src/main/scala/gopher/channels/OrInput.scala b/0.99.x/src/main/scala/gopher/channels/OrInput.scala deleted file mode 100644 index 481506f6..00000000 --- a/0.99.x/src/main/scala/gopher/channels/OrInput.scala +++ /dev/null @@ -1,62 +0,0 @@ -package gopher.channels - -import scala.annotation._ -import scala.concurrent._ -import scala.util._ -import java.util.concurrent.atomic.AtomicBoolean - -import gopher._ - -/** - * Input, which combine two other inputs. - * - * can be created with '|' operator. - * - * {{{ - * val x = read(x|y) - * }}} - */ -class OrInput[A](x:Input[A],y:Input[A]) extends Input[A] -{ - - - def cbread[B](f: ContRead[A,B] => Option[ContRead.In[A] => Future[Continuated[B]]], flwt: FlowTermination[B] ): Unit = - { - val cBegin = new AtomicBoolean(false) - val cEnd = new AtomicBoolean(false) - - @tailrec - def cf(cont:ContRead[A,B]): Option[ContRead.In[A]=>Future[Continuated[B]]] = - { - if (cBegin.compareAndSet(false,true)) { - f(cont) match { - case sf1@Some(f1) => cEnd.set(true) - sf1 - case None => cBegin.set(false) - None - } - } else if (cEnd.get()) { - None - } else { - // own spin-lock: wait until second instance will completely processed. - // this is near impossible situation: when both inputs and outputs - // raise signal at the same time, A start check aviability of handler, - // B enter the section when aviability checking is not finished. - while(cBegin.get() && !cEnd.get()) { - // TODO: slip tick ? - Thread.`yield`(); - } - if (cEnd.get()) None else cf(cont) - } - } - x.cbread(cf, flwt) - y.cbread(cf, flwt) - } - - // | is left-associative, so (x|y|z|v).api better be v.api, - def api = y.api - - override def toString() = s"(${x}|${y})" - -} - diff --git a/0.99.x/src/main/scala/gopher/channels/Output.scala b/0.99.x/src/main/scala/gopher/channels/Output.scala deleted file mode 100644 index 38984405..00000000 --- a/0.99.x/src/main/scala/gopher/channels/Output.scala +++ /dev/null @@ -1,172 +0,0 @@ -package gopher.channels - -import scala.concurrent._ -import scala.concurrent.duration._ -import scala.async.Async._ -import scala.language.experimental.macros -import scala.reflect.macros.blackbox.Context -import scala.reflect.api._ -import gopher._ - -/** - * Entity, where we can write objects of type A. - * - */ -trait Output[A] extends GopherAPIProvider -{ - - thisOutput => - - type ~> = A - type writeExp[X] = A - type write = A - - - /** - * apply f and send result to channels processor. - */ - def cbwrite[B](f: ContWrite[A,B] => Option[ - (A,Future[Continuated[B]]) - ], - ft: FlowTermination[B]): Unit - - def api: GopherAPI - - def awrite(a:A):Future[A] = - { - val ft = PromiseFlowTermination[A]() - cbwrite[A]( cont => { - Some((a,Future.successful(Done(a,ft)))) - }, - ft - ) - ft.future - } - - /** - * 'blocking' write of 'a' to channel. - * Note, that this method can be called only inside - * 'go' or 'async' blocks. - **/ - def write(a:A):A = macro Output.writeImpl[A] - - /** - * shortcut for blocking write. - */ - def <~ (a:A):Output[A] = macro Output.writeWithBuilderImpl[A] - - /** - * shortcut for blocking write. - */ - def !(a:A):Unit = macro Output.writeImpl[A] - - - def awriteAll[C <: Iterable[A]](c:C):Future[Unit] = - { - if (c.isEmpty) { - Future successful (()) - } else { - val ft = PromiseFlowTermination[Unit] - val it = c.iterator - def f(cont:ContWrite[A,Unit]):Option[(A,Future[Continuated[Unit]])]= - { - val n = it.next() - if (it.hasNext) { - Some((n,Future successful ContWrite(f,this,ft))) - } else { - Some((n, Future successful Done((), ft) )) - } - } - cbwrite(f,ft) - ft.future - } - } - - def writeAll[C <: Iterable[A]](it:C):Unit = macro Output.writeAllImpl[A,C] - - def unfold[S](s:S)(f:S=>(S,A)):Unit = - { - var ca=f(s) - val fs = api.select.forever - fs.writingWithFlowTerminationAsync[A](this,ca._2,(ec,ft,a) => Future successful {ca=f(ca._1)} ) - fs.go - } - - /** - *provide pair from Output and Input `(ready, timeouts)` such that writing to `ready` - * will case writing to `output` and if it was not completed during ``timeout` than - * appropriative duration will be availabe in `timeouts` input. - * - *``` - *val (chReady, chTimeouts) = ch withOutputTimeouts (5 seconds) - *select.forever { - * case x: chReady.write if (x==somethingToWrite) => - * Console.println(s" \${x} send") - * case t: chTimeouts.read => - * Console.println(s"timeout during writing") - *} - *``` - **/ - def withOutputTimeouts(timeout: FiniteDuration): (Output[A],Input[FiniteDuration]) = - new OutputWithTimeouts(this, timeout).pair - - /** - * before passing value to output, apply g to one. - */ - def premap[C](g: C => A): Output[C] = new Output[C] { - - - override def api: GopherAPI = thisOutput.api - - override def cbwrite[B](f: (ContWrite[C, B]) => Option[(C, Future[Continuated[B]])], ft: FlowTermination[B]): Unit = { - def gf(cw:ContWrite[A,B]):Option[(A,Future[Continuated[B]])] = - { - f(ContWrite(f,this,cw.flowTermination)) map { case (c,fc) => - (g(c),fc) - } - } - thisOutput.cbwrite[B](gf,ft) - } - - } - - /** - * alias for premap - */ - def pam[B](g: B=>A): Output[B] = premap[B](g) - - -} - -object Output -{ - - def writeImpl[A](c:Context)(a:c.Expr[A]):c.Expr[A] = - { - import c.universe._ - c.Expr[A](q"scala.async.Async.await(${c.prefix}.awrite(${a}))") - } - - def writeAllImpl[A,C](c:Context)(it:c.Expr[C]):c.Expr[Unit] = - { - import c.universe._ - c.Expr[Unit](q"scala.async.Async.await(${c.prefix}.writeAll(${it}))") - } - - - def writeWithBuilderImpl[A](c:Context)(a:c.Expr[A]):c.Expr[Output[A]] = - { - import c.universe._ - val retval = c.Expr[Output[A]]( - q"""{ - val prefix = ${c.prefix} - scala.async.Async.await{prefix.awrite(${a});{}} - prefix - } - """ - ) - retval - } - - -} diff --git a/0.99.x/src/main/scala/gopher/channels/OutputWithTimeouts.scala b/0.99.x/src/main/scala/gopher/channels/OutputWithTimeouts.scala deleted file mode 100644 index 64e7c401..00000000 --- a/0.99.x/src/main/scala/gopher/channels/OutputWithTimeouts.scala +++ /dev/null @@ -1,53 +0,0 @@ -package gopher.channels - -import scala.concurrent._ -import scala.concurrent.duration._ -import scala.async.Async._ -import scala.language.experimental.macros -import scala.reflect.api._ -import gopher._ - -/** - * - * - */ -class OutputWithTimeouts[A](origin: Output[A], timeout: FiniteDuration) -{ - - - def pair:(Output[A],Input[FiniteDuration]) = (wrapped, timeouts) - - val timeouts: gopher.channels.Channel[FiniteDuration] = api.makeChannel[FiniteDuration]() - - val wrapped: Output[A] = new Output[A] { - - def cbwrite[B](f: ContWrite[A,B] => Option[(A,Future[Continuated[B]])], - ft: FlowTermination[B]): Unit = - { - val c = api.actorSystem.scheduler.scheduleOnce(timeout){ - timeouts.awrite(timeout) - }(api.gopherExecutionContext) - def fIn(cont: ContWrite[A,B]):Option[(A,Future[Continuated[B]])] = - { - try { - f(ContWrite(f,this,ft)) map { case (a,next) => - c.cancel() - (a,next) - } - } catch { - case ex:ChannelClosedException => timeouts.close() - throw ex - } - } - - origin.cbwrite(fIn,ft) - } - - def api = origin.api - - } - - def api = origin.api - -} - diff --git a/0.99.x/src/main/scala/gopher/channels/PromiseFlowTermination.scala b/0.99.x/src/main/scala/gopher/channels/PromiseFlowTermination.scala deleted file mode 100644 index a50cb949..00000000 --- a/0.99.x/src/main/scala/gopher/channels/PromiseFlowTermination.scala +++ /dev/null @@ -1,50 +0,0 @@ -package gopher.channels - -import scala.language.postfixOps -import scala.concurrent._ -import scala.util._ -import gopher._ - -trait PromiseFlowTermination[A] extends FlowTermination[A] -{ - - def doThrow(e: Throwable): Unit = - { - if (isCompleted) { - import ExecutionContext.Implicits.global - p.future.onComplete{ - case Success(x) => - // success was before throw, ignoring. - case Failure(prevEx) => - //prevEx.printStackTrace(); - } - } else { - p failure e - } - } - - def doExit(a: A): A = - { - p trySuccess a - a - } - - def future = - p future - - def isCompleted = p.isCompleted - - def throwIfNotCompleted(ex: Throwable):Unit = - p.tryFailure(ex) - - def completeWith(other: Future[A]): Unit = - p.completeWith(other) - - private[this] val p = Promise[A]() - -} - -object PromiseFlowTermination -{ - def apply[A]() = new PromiseFlowTermination[A]() {} -} diff --git a/0.99.x/src/main/scala/gopher/channels/Selector.scala b/0.99.x/src/main/scala/gopher/channels/Selector.scala deleted file mode 100644 index 3fa6943a..00000000 --- a/0.99.x/src/main/scala/gopher/channels/Selector.scala +++ /dev/null @@ -1,297 +0,0 @@ -package gopher.channels - -import gopher._ -import akka.actor._ -import akka.pattern._ - -import scala.concurrent._ -import scala.concurrent.duration._ -import scala.language.postfixOps -import scala.util._ -import java.util.concurrent.atomic.{AtomicBoolean, AtomicLong, AtomicReference} -import java.util.concurrent.ConcurrentLinkedQueue - - -class Selector[A](api: GopherAPI) extends PromiseFlowTermination[A] { - - thisSelector => - - def addReader[E](ch: Input[E], f: ContRead[E, A] => Option[ContRead.In[E] => Future[Continuated[A]]]): Unit = { - waiters add makeLocked(ContRead(f, ch, this)) - } - - def addWriter[E](ch: Output[E], f: ContWrite[E, A] => Option[(E, Future[Continuated[A]])]): Unit = { - waiters add makeLocked(ContWrite(f, ch, this)) - } - - def addTimeout(timeout: FiniteDuration, f: Skip[A] => Option[Future[Continuated[A]]]): Unit = { - if (!timeoutRecord.isDefined) { - timeoutRecord.lastNOperations = nOperations.get - timeoutRecord.timeout = timeout - timeoutRecord.waiter = makeLocked(Skip(f, this)) - } else { - throw new IllegalStateException("select must have only one timeout entry") - } - } - - def addErrorHandler(f: (ExecutionContext, FlowTermination[A], Continuated[A], Throwable) => Future[Continuated[A]]): Unit = { - errorHandler.lazySet(f) - } - - def run: Future[A] = { - sendWaits() - if (timeoutRecord.isDefined) { - scheduleTimeout() - } - future - } - - - private[channels] def lockedRead[E](f: ContRead.AuxF[E, A], ch: Input[E], ft: FlowTermination[A]): ContRead.AuxF[E, A] = { - val cont0 = ContRead(f,ch,ft) - def f1(cont: ContRead[E, A]): Option[ContRead.In[E] => Future[Continuated[A]]] = { - tryLocked(f(cont0), cont, "read") map { q => - in => - unlockAfter( - try { - errorHandled(q(in), cont0) - }catch { - case e: Throwable => ft.doThrow(e) - Future successful Never - }, - ft, "read") - } - } - - f1 - } - - - - private[channels] def lockedWrite[E](f: ContWrite.AuxF[E, A], ch: Output[E], ft: FlowTermination[A]): ContWrite.AuxF[E, A] = { (cont) => - val cont0 = ContWrite(f, ch, ft) - tryLocked(f(cont0), cont, "write") map { - case (a, future) => - (a, unlockAfter(errorHandled(future, cont0), ft, "write")) - } - } - - private[channels] def lockedSkip(f: Skip.AuxF[A], ft: FlowTermination[A]): Skip.AuxF[A] = { cont => - // TODO: check, maybe pass cont in this situation is enough ? - val cont0 = Skip(f, ft) - tryLocked(f(cont0), cont, "skip") map { f => - unlockAfter(errorHandled(f, cont0), ft, "skip") - } - } - - private[channels] def makeLocked(block: Continuated[A]): Continuated[A] = { - block match { - case cr@ContRead(f, ch, ft) => ContRead(lockedRead(f, ch, ft), ch, ft) - case cw@ContWrite(f, ch, ft) => ContWrite(lockedWrite(f, ch, ft), ch, ft) - case sk@Skip(f, ft) => Skip(lockedSkip(f, ft), ft) - case dn@Done(f, ft) => dn - case Never => Never - } - } - - - - - @inline - private[this] def tryLocked[X](body: => Option[X], cont: FlowContinuated[A], dstr: String): Option[X] = - if (tryLock()) { - try { - body match { - case None => mustUnlock(dstr, cont.flowTermination) - waiters add cont - None - case sx@Some(x) => - nOperations.incrementAndGet() - sx - } - } catch { - case ex: Throwable => - if (!(errorHandler.get() eq null)) { - errorHandler.get() - } - unlock(dstr) - cont.flowTermination.doThrow(ex) - None - } - } else { - toWaiters(cont) - None - } - - @inline - private[channels] def errorHandled(f: Future[Continuated[A]], cont: Continuated[A]): Future[Continuated[A]] = - { - f.recoverWith{ - case ex => - val h = errorHandler.get() - if (h eq null) Future failed ex - else h(executionContext, this, cont,ex) - } - } - - @inline - private[channels] def unlockAfter(f: Future[Continuated[A]], flowTermination: FlowTermination[A], dstr: String): Future[Continuated[A]] = { - f.transform( - next => { - if (mustUnlock(dstr, flowTermination)) { - if (timeoutRecord.isDefined) - scheduleTimeout() - makeLocked(next) - } else Never - }, - ex => { - mustUnlock(dstr, flowTermination); - ex - } - ) - } - - //private[this] def ifNotCompleted1[X](ft:FlowTermination[A],f:X => Future[Continuated[A]]) - - private[this] def toWaiters(cont:Continuated[A]):Unit= - { - waiters.add(cont) - if (!lockFlag.get()) { - // possible, when we call waiters.put locked, but then in other thread it was - // unlocked and queue cleaned before waiters modify one. - sendWaits() - } - } - - - private[this] def scheduleTimeout():Unit = - { - - def tickOperation():Unit = - { - if (!isCompleted && timeoutRecord.isDefined) { - val currentNOperations = nOperations.get() - if (currentNOperations == timeoutRecord.lastNOperations) { - // fire - timeoutRecord.waiter match { - // TODO: add timeout field to skip - case sk@Skip(f,ft) => f(sk) foreach { futureNext => - futureNext.onComplete { - case Success(next) => if (!isCompleted) { - next match { - case sk@Skip(f,ft) if (ft eq this) => timeoutRecord.waiter = sk - case other => - timeoutRecord.waiter = Never - api.continuatedProcessorRef ! other - } - } - case Failure(ex) => if (!isCompleted) ft.doThrow(ex) - } - } - case other => api.continuatedProcessorRef ! other - } - } - } - } - - if (timeoutRecord.isDefined) { - // TODO: make CAS - timeoutRecord.lastNOperations = nOperations.get() - val scheduler = api.actorSystem.scheduler - scheduler.scheduleOnce(timeoutRecord.timeout)(tickOperation) - } - - } - - def isLocked: Boolean = lockFlag.get() - - private[this] def tryLock(): Boolean = lockFlag.compareAndSet(false,true) - - private[this] def unlock(debugFrom: String): Boolean = - { - val retval = lockFlag.compareAndSet(true,false) - //if (retval) { - sendWaits() - //} - retval - } - - private[this] def mustUnlock(debugFrom: String, ft: FlowTermination[_]): Boolean = - { - if (!unlock(debugFrom)) { - try { - throw new IllegalStateException("other fiber occypied select 'lock'") - }catch{ - //!!! - case ex: Exception => ft.doThrow(ex) - ex.printStackTrace() - } - false - } else true - } - - - - private[this] def sendWaits(waiters: ConcurrentLinkedQueue[Continuated[A]] = waiters): Unit = - { - // concurrent structure fpr priority queue - var skips = List[Continuated[A]]() - var nSend = 0 - while(!waiters.isEmpty && !lockFlag.get()) { - val c = waiters.poll - if (!(c eq null)) { - nSend = nSend + 1 - c match { - case sk@Skip(_,_) => - skips = c.asInstanceOf[Continuated[A]]::skips - case _ => - processor ! c - } - } - } - if (!lockFlag.get) { - //planIdle - //TODO: plan instead direct send. - for(c <- skips) { - (processor.ask(c)(10 seconds)).foreach(x => - waiters.add(x.asInstanceOf[Continuated[A]]) - ) - } - } - } - - - private[this] val log = api.actorSystem.log - - // false when unlocked, true otherwise. - private[this] val lockFlag: AtomicBoolean = new AtomicBoolean(false) - - // number of operations, increased during each lock/unlock. - // used for idle and timeout detection - private[channels] val nOperations = new AtomicLong(); - - private[this] val waiters: ConcurrentLinkedQueue[Continuated[A]] = new ConcurrentLinkedQueue() - - private[this] class TimeoutRecord( - var lastNOperations: Long, - var timeout: FiniteDuration, - var waiter: Continuated[A] - ) { - def isDefined:Boolean = (waiter != Never) - } - - - private[this] val timeoutRecord: TimeoutRecord = new TimeoutRecord(0L,0 milliseconds, Never) - - private[this] val errorHandler = new AtomicReference[(ExecutionContext, FlowTermination[A], Continuated[A], Throwable) => Future[Continuated[A]]]() - private[this] val inError = new AtomicBoolean(false) - - private[this] val processor = api.continuatedProcessorRef - - private[this] implicit val executionContext: ExecutionContext = api.gopherExecutionContext - - -} - - - diff --git a/0.99.x/src/main/scala/gopher/channels/SelectorArguments.scala b/0.99.x/src/main/scala/gopher/channels/SelectorArguments.scala deleted file mode 100644 index c07a1ede..00000000 --- a/0.99.x/src/main/scala/gopher/channels/SelectorArguments.scala +++ /dev/null @@ -1,131 +0,0 @@ -package gopher.channels - -import gopher._ - -import scala.concurrent._ -import scala.util.Try - -sealed trait ReadSelectorArgument[A,B] -{ - def normalizedFun: ContRead[A,B] => Option[ContRead.In[A]=>Future[Continuated[B]]] -} - -case class AsyncFullReadSelectorArgument[A,B]( - f: ContRead[A,B] => Option[ContRead.In[A]=>Future[Continuated[B]]] - ) extends ReadSelectorArgument[A,B] -{ - def normalizedFun = f -} - -case class AsyncNoOptionReadSelectorArgument[A,B]( - f: ContRead[A,B] => (ContRead.In[A]=>Future[Continuated[B]]) - ) extends ReadSelectorArgument[A,B] -{ - def normalizedFun = ( cont => Some(f(cont)) ) -} - -case class AsyncNoGenReadSelectorArgument[A,B]( - f: ContRead[A,B] => (A=>Future[Continuated[B]]) - ) extends ReadSelectorArgument[A,B] -{ - def normalizedFun = ( cont => Some(ContRead.liftIn(cont)(f(cont))) ) -} - -case class AsyncPairReadSelectorArgument[A,B]( - f: (A, ContRead[A,B]) => Future[Continuated[B]] - ) extends ReadSelectorArgument[A,B] -{ - def normalizedFun = ( c => Some(ContRead.liftIn(c)(f(_,c))) ) -} - -case class SyncReadSelectorArgument[A,B]( - f: ContRead[A,B] => (ContRead.In[A] => Continuated[B]) - ) extends ReadSelectorArgument[A,B] -{ - def normalizedFun = ( cont => Some( gen => Future successful f(cont)(gen) ) ) -} - -case class SyncPairReadSelectorArgument[A,B]( - f: (A, ContRead[A,B]) => Continuated[B] - ) extends ReadSelectorArgument[A,B] -{ - def normalizedFun = ( c => Some(ContRead.liftIn(c)(a => Future successful f(a,c))) ) -} - -sealed trait WriteSelectorArgument[A,B] -{ - def normalizedFun: ContWrite[A,B] => Option[(A,Future[Continuated[B]])] -} - -case class AsyncFullWriteSelectorArgument[A,B]( - f: ContWrite[A,B] => Option[(A,Future[Continuated[B]])] - ) extends WriteSelectorArgument[A,B] -{ - def normalizedFun = f -} - -case class AsyncNoOptWriteSelectorArgument[A,B]( - f: ContWrite[A,B] => (A,Future[Continuated[B]]) - ) extends WriteSelectorArgument[A,B] -{ - def normalizedFun = (c => Some(f(c))) -} - -case class SyncWriteSelectorArgument[A,B]( - f: ContWrite[A,B] => (A,Continuated[B]) - ) extends WriteSelectorArgument[A,B] -{ - def normalizedFun = {c => - val (a, next) = f(c) - Some((a,Future successful next)) - } - -} - -sealed trait SkipSelectorArgument[A] -{ - def normalizedFun: Skip[A] => Option[Future[Continuated[A]]] -} - -case class AsyncFullSkipSelectorArgument[A]( - f: Skip[A] => Option[Future[Continuated[A]]] - ) extends SkipSelectorArgument[A] -{ - def normalizedFun = f -} - -case class AsyncNoOptSkipSelectorArgument[A]( - f: Skip[A] => Future[Continuated[A]] - ) extends SkipSelectorArgument[A] -{ - def normalizedFun = { c => Some(f(c)) } -} - -case class SyncSelectorArgument[A]( - f: Skip[A] => Continuated[A] - ) extends SkipSelectorArgument[A] -{ - def normalizedFun = { c => Some(Future successful f(c)) } -} - -sealed trait ErrorSelectorArgument[A] -{ - - def normalizedFun: (ExecutionContext, FlowTermination[A], Continuated[A], Throwable) => Future[Continuated[A]] - -} - -case class AsyncErrorSelectorArgument[A]( - f: (ExecutionContext, FlowTermination[A], Continuated[A], Throwable) => Future[Continuated[A]] - ) extends ErrorSelectorArgument[A] -{ - override def normalizedFun: (ExecutionContext, FlowTermination[A], Continuated[A], Throwable) => Future[Continuated[A]] = f -} - -case class SyncErrorSelectorArgument[A]( - f: (ExecutionContext, FlowTermination[A], Continuated[A], Throwable) => Continuated[A] - ) extends ErrorSelectorArgument[A] -{ - override def normalizedFun: (ExecutionContext, FlowTermination[A], Continuated[A], Throwable) => Future[Continuated[A]] = - (ec,ft,cont,ex) => Future.fromTry(Try(f(ec,ft,cont,ex))) -} diff --git a/0.99.x/src/main/scala/gopher/channels/SelectorBuilder.scala b/0.99.x/src/main/scala/gopher/channels/SelectorBuilder.scala deleted file mode 100644 index 7e8066f8..00000000 --- a/0.99.x/src/main/scala/gopher/channels/SelectorBuilder.scala +++ /dev/null @@ -1,635 +0,0 @@ -package gopher.channels - -import scala.language.experimental.macros -import scala.reflect.macros.blackbox.Context -import scala.reflect.api._ -import gopher._ -import gopher.util._ -import gopher.goasync._ - -import scala.concurrent._ -import scala.concurrent.duration._ -import scala.annotation.unchecked._ -import scala.reflect.macros.blackbox - -trait SelectorBuilder[A] -{ - - type timeout = FiniteDuration - - def api: GopherAPI - - def onRead[E](ch:Input[E])(arg: ReadSelectorArgument[E,A]): this.type = - { - selector.addReader(ch,arg.normalizedFun) - this - } - - def onWrite[E](ch:Output[E])(arg: WriteSelectorArgument[E,A]): this.type = - { - selector.addWriter(ch,arg.normalizedFun) - this - } - - def onIdle(arg: SkipSelectorArgument[A]): this.type = - { - withTimeout(api.idleTimeout)(arg.normalizedFun) - this - } - - def onTimeout(t:FiniteDuration)(arg: SkipSelectorArgument[A]): this.type = - withTimeout(t)(arg.normalizedFun) - - def onError(arg: ErrorSelectorArgument[A]): this.type = { - withError(arg.normalizedFun) - this - } - - @inline - def withReader[B](ch:Input[B], f: ContRead[B,A] => Option[ContRead.In[B]=>Future[Continuated[A]]]): this.type = - { - selector.addReader(ch,f) - this - } - - @inline - def withWriter[B](ch:Output[B], f: ContWrite[B,A] => Option[(B,Future[Continuated[A]])] ): this.type = - { - selector.addWriter(ch,f) - this - } - - @inline - def withIdle(f: Skip[A] => Option[Future[Continuated[A]]]):this.type = - { - withTimeout(api.idleTimeout)(f) - } - - @inline - def withTimeout(t:FiniteDuration)(f: Skip[A] => Option[Future[Continuated[A]]]):this.type = - { - selector.addTimeout(t,f) - this - } - - @inline - def withError(f: (ExecutionContext, FlowTermination[A], Continuated[A], Throwable) =>Future[Continuated[A]]):this.type = { - selector.addErrorHandler(f) - this - } - - final def go: Future[A] = selectorRun - - // for call from SelectorTransforment wich have another 'go' - def selectorRun: Future[A] = selector.run - - implicit def ec: ExecutionContext = api.gopherExecutionContext - - private[gopher] val selector=new Selector[A](api) - - // used for reading from future - @inline - def futureInput[A](f:Future[A]):FutureInput[A]=api.futureInput(f) - -} - - - -class SelectorBuilderImpl(val c: Context) extends ASTUtilImpl -{ - - import c.universe._ - - trait ActionGenerator { - - def genReading(builder: TermName, channel:Tree, param: ValDef, body: Tree): Tree - - def genWriting(builder: TermName, channel:Tree, expr: Tree, param: ValDef, body: Tree):Tree - - def genDone(builder: TermName, channel: Tree, param: ValDef, body: Tree): Tree - - def genError(builder: TermName, selector: Tree, param: ValDef, body: Tree): Tree - - } - - class DefaultActionGenerator extends ActionGenerator { - - override def genReading(builder: TermName, readed: Tree, param: ValDef, body: Tree): Tree = { - val channel = readed match { - case q"gopher.`package`.FutureWithRead[${t2}](${future})" => - q"${builder}.futureInput(${future})" - case _ => - if (readed.tpe eq null) { - readed // it was generated by us - } else if (readed.tpe <:< typeOf[gopher.channels.Input[_]]) { - readed - } else if (readed.tpe <:< typeOf[gopher.`package`.FutureWithRead[_]]) { - q"${builder}.futureInput(${readed}.aread)" - } else { - c.abort(readed.pos, "reading in select pattern guide must be channel or future, we have:" + readed.tpe) - } - } - q"${builder}.reading(${channel}){ ${param} => ${body} }" - } - - override def genWriting(builder: TermName, channel: Tree, expr: Tree, param: ValDef, body: Tree): Tree = - { - q"${builder}.writing(${channel},${expr})(${param} => ${body} )" - } - - - override def genDone(builder: TermName, channel: Tree, param: ValDef, body: Tree): Tree = - q"${builder}.onDone(${channel})(${param} => ${body} )" - - override def genError(builder: TermName, selector: Tree, param: ValDef, body: Tree): Tree = { - q"${builder}.handleError(${param} => ${body} )" - } - - } - - val defaultActionGenerator = new DefaultActionGenerator() - - - def foreach[T](f:c.Expr[Any=>T]):c.Expr[T] = - { - val builder = f.tree match { - case Function(forvals,Match(choice,cases)) => - // TOD: check that forvals and choice are same - foreachBuildMatch(cases) - // TODO: think, are we need syntax with common-expr ? - //case Function(forvals,Block(commonExpr,Match(choice,cases))) => - // foreachBuildMatch(forvals,choice,cases, commonExpr) - case Function(a,b) => - c.abort(f.tree.pos, "match expected in gopher select loop, have: ${MacroUtil.shortString(b)} "); - case _ => { - c.abort(f.tree.pos, "match expected in gopher select loop, have: ${MacroUtil.shortString(f.tree)}"); - } - } - c.Expr[T](MacroUtil.cleanUntypecheck(c)(q"gopher.goasync.AsyncWrapper.await(${builder}.go)")) - } - - def foreachBuildMatch(cases:List[c.universe.CaseDef]):c.Tree = - { - import c.universe._ - val bn = TermName(c.freshName) - val calls = transformSelectMatch(bn,cases, defaultActionGenerator) - q"""..${q"val ${bn} = ${c.prefix}" :: calls}""" - } - - - def transformSelectMatch(bn: c.universe.TermName, cases:List[c.universe.CaseDef], actionGenerator: ActionGenerator):List[c.Tree] = - { - cases.zipWithIndex map { case (cs, i) => - transformSelectCaseDef(bn,cs, i, actionGenerator) - } - } - - - - - - def transformSelectCaseDef(builderName:c.TermName, - caseDef: c.universe.CaseDef, - caseDefIndex: Int, - actionGenerator: ActionGenerator):c.Tree = { - - val symbolsToErase: Set[Symbol] = Option(caseDef.pat.symbol).toSet.flatMap { (sym: Symbol) => - Set(sym) ++ Option(sym.owner).toSet - } - - // when we split cassDef on few functions, than sometines, symbols - // entries in identifier tree are not cleared. - // So, we 'reset' symbols which belong to caseDef which will be erased by macros - // //TODO: check, may be will be better to use scala-compiler internal API and changeOwner instead. - // yet one alternative - untypedef 'up' term - def clearCaseDefOwner(oldName: c.Name, newName: c.TermName, tree: Tree): Tree = { - val oldTermName = oldName.toTermName - - def changeName(name: c.TermName): c.TermName = - if (name == oldTermName) newName else name - - def ownerWillBeErased(sym: Symbol): Boolean = - symbolsToErase.contains(sym) - - class ClearTransformer extends Transformer { - - var insideMustBeErased: Boolean = false - - override def transform(tree: Tree): Tree = { - tree match { - case Typed(ident@Ident(`oldTermName`), _) => if (ident.symbol != null && ownerWillBeErased(ident.symbol)) - atPos(tree.pos)(Ident(newName)) - else - super.transform(tree) - case ident@Ident(`oldTermName`) => if (ident.symbol != null && ownerWillBeErased(ident.symbol)) - atPos(tree.pos)(Ident(newName)) - else - super.transform(tree) - case _ => - if (tree.symbol != null && tree.symbol != NoSymbol) { - if (ownerWillBeErased(tree.symbol)) { - var prevMustBeErased = insideMustBeErased - insideMustBeErased = true - try { - val (done, rtree) = doClear(tree) - insideMustBeErased = prevMustBeErased - if (done) { - rtree - } else { - super.transform(tree) - } - } catch { - case ex: Exception => - System.err.println(s"ex, tree.symbol=${tree.symbol}") - ex.printStackTrace() - throw ex - } - } else super.transform(tree) - } else { - if (false && insideMustBeErased) { - val (done, rtree) = doClear(tree) - if (done) rtree else super.transform(rtree) - } else - super.transform(tree) - } - } - } - - def doClear(tree: c.Tree): (Boolean, c.Tree) = { - tree match { - case Ident(name: TermName) => - (true, atPos(tree.pos)(Ident(changeName(name)))) - case Bind(name: TermName, body) => - (true, atPos(tree.pos)(Bind(changeName(name), transform(body)))) - case ValDef(mods, name, tpt, rhs) => - (true, atPos(tree.pos)(ValDef(mods, changeName(name), transform(tpt), transform(rhs)))) - case Select(Ident(name: TermName), proj) => - (true, atPos(tree.pos)(Select(Ident(changeName(name)), proj))) - case _ => - // (false, tree) - throw new IllegalStateException("unexpected shapr") - c.abort(tree.pos, - """Unexpected shape for tree with caseDef owner, which erased by macro, - please, fire bug-report to scala-gopher, raw=""" + showRaw(tree)) - } - } - - } - val transformer = new ClearTransformer() - transformer.transform(tree) - } - - def retrieveOriginal(tp: Tree): Tree = - tp match { - case tpt: TypeTree => if (tpt.original.isEmpty) tpt else tpt.original - case _ => tp - } - - def unUnapplyPattern(x: Tree): Tree = - x match { - case Bind(name, UnApply(_, List(t@Typed(_, _)))) => Bind(name, t) - case _ => x - } - - - - val acceptor = new SelectCaseDefAcceptor[TermName, Tree] { - - - private def paramWithTransformedBody(v: c.universe.TermName, tp: c.universe.Tree): (ValDef, Tree) = { - val newName = v - val tpoa = clearCaseDefOwner(v, newName, tp) - val param = ValDef(Modifiers(Flag.PARAM), newName, tpoa, EmptyTree) - val body = clearCaseDefOwner(v, newName, caseDef.body) - (param, body) - } - - override def onRead(bn: TermName, v: TermName, ch: Tree, tp: Tree): Tree = { - val (param, body) = paramWithTransformedBody(v, tp) - val reading = actionGenerator.genReading(builderName, ch, param, body) - atPos(caseDef.pat.pos)(reading) - } - - override def onWrite(bn: TermName, v: TermName, expr: Tree, ch: Tree, tp: Tree): Tree = { - val (param, body) = paramWithTransformedBody(v, tp) - val writing = actionGenerator.genWriting(builderName, ch, expr, param, body) - atPos(caseDef.pat.pos)(writing) - } - - override def onSelectTimeout(bn: TermName, v: TermName, select: Tree, tp: Tree): Tree = { - val (param, body) = paramWithTransformedBody(v, tp) - val expression = if (!caseDef.guard.isEmpty) { - parseGuardInSelectorCaseDef(v, caseDef.guard) - } else { - atPos(caseDef.pat.pos)(q"implicitly[akka.util.Timeout].duration") - } - val timeout = q"${builderName}.timeout(${expression})(${param} => ${body} )" - atPos(caseDef.pat.pos)(timeout) - } - - override def onIdle(bn: TermName): Tree = { - if (!caseDef.guard.isEmpty) { - c.abort(caseDef.guard.pos, "guard is not supported in select case") - } - val r = q"${builderName}.timeout(${builderName}.api.idleTimeout)( _ => ${caseDef.body})" - atPos(caseDef.pat.pos)(r) - } - - override def onDone(bn: TermName, v: TermName, ch: Tree, tp: Tree): Tree = { - val ch1 = q"${ch}.done" - onRead(bn, v, ch1, tp) - } - - override def onError(bn: TermName, v: TermName, select: Tree, tp: Tree): Tree = - { - val (param, body) = paramWithTransformedBody(v,tp) - val r = actionGenerator.genError(bn,select,param,body) - atPos(caseDef.pat.pos)(r) - } - - } - - acceptSelectCaseDefPattern(caseDef, builderName, acceptor) - - } - - - def mapBuildMatch[T:c.WeakTypeTag](cases:List[c.universe.CaseDef], actionGenerator: ActionGenerator):c.Tree = - { - val bn = TermName(c.freshName) - val calls = transformSelectMatch(bn,cases,actionGenerator) - q"""..${q"val ${bn} = ${c.prefix}.inputBuilder[${weakTypeOf[T]}]()" :: calls}""" - } - - def map[T:c.WeakTypeTag](f:c.Expr[Any=>T]):c.Expr[CloseableInput[T]] = - { - val builder = f.tree match { - case Function(forvals,Match(choice,cases)) => - mapBuildMatch[T](cases,defaultActionGenerator) - case Function(a,b) => - c.abort(f.tree.pos, "match expected in gopher select map, have: ${MacroUtil.shortString(b)} "); - case _ => - c.abort(f.tree.pos, "match expected in gopher select map, have: ${MacroUtil.shortString(f.tree)}"); - - } - c.Expr[CloseableInput[T]](MacroUtil.cleanUntypecheck(c)(q"${builder}.started")) - } - - def builder[T](f:c.Expr[PartialFunction[Any,T]]):c.Tree = - { - f.tree match { - case q"{case ..$cases}" => - foreachBuildMatch(cases) - case _ => c.abort(f.tree.pos,"expected partial function with syntax case ... =>, have ${MacroUtil.shortString(f.tree)}"); - } - } - - def apply[T](f:c.Expr[PartialFunction[Any,T]]):c.Expr[Future[T]] = - { - val b = builder[T](f) - c.Expr[Future[T]](c.untypecheck(q"${b}.go")) - } - - /** - * processor: loop => just add waiters to this selector. - */ - def loop[T](f:c.Expr[PartialFunction[Any,T]]):c.Expr[Unit] = - { - val b = builder[T](f) - c.Expr[Unit](c.untypecheck(q"{selectorInit = ()=>${b}; selectorInit()}")) - } - - def input[T:c.WeakTypeTag](f:c.Expr[PartialFunction[Any,T]]):c.Expr[Input[T]] = - { - val builder = f.tree match { - case q"{case ..$cases}" => - mapBuildMatch[T](cases,defaultActionGenerator) - case _ => c.abort(f.tree.pos,"expected partial function with syntax case ... =>, have ${MacroUtil.shortString(f.tree)}"); - } - c.Expr[Input[T]](MacroUtil.cleanUntypecheck(c)(q"${builder}.started")) - } - - trait SelectCaseDefAcceptor[A,B] { - def onRead(s:A, v: TermName, ch:Tree, tp: Tree):B - def onWrite(s:A, v:TermName, expression: Tree, ch:Tree, tp: Tree ):B - def onSelectTimeout(s:A, v: TermName, select:Tree, tp: Tree):B - def onIdle(s:A):B - def onDone(s:A,v: TermName,ch:Tree, tp: Tree):B - def onError(s:A, v: TermName, select:Tree, tp: Tree): B - } - - //TODO: generalize and merge with parsing in SelectorBuilderImpl - def acceptSelectCaseDefPattern[A,B](caseDef:CaseDef, - a: A, - acceptor: SelectCaseDefAcceptor[A,B]):B = - { - - def acceptTypeTree(tp:Tree, termName:TermName): B = { - val unwrappedType = MacroUtil.unwrapOriginUnannotatedType(c)(tp) - unwrappedType match { - case Select(ch,TypeName("read")) => acceptor.onRead(a,termName,ch,unwrappedType) - case Select(ch,TypeName("write")) => - val expression = if (!caseDef.guard.isEmpty) { - parseGuardInSelectorCaseDef(termName, caseDef.guard) - } else { - atPos(caseDef.pat.pos)(Ident(termName)) - } - acceptor.onWrite(a,termName, expression, ch, unwrappedType) - case Select(select,TypeName("timeout")) => acceptor.onSelectTimeout(a,termName,select, tp) - case Select(ch,TypeName("done")) => acceptor.onDone(a,termName,ch,tp) - case Select(select,TypeName("error")) => acceptor.onError(a,termName,select,tp) - case _ => - if (caseDef.guard.isEmpty) { - c.abort(tp.pos, - """match pattern in select without guard must be in form x:channel.write or x:channel.read - |our raw caseDef: - """.stripMargin + showRaw(caseDef)) - } else { - parseGuardInSelectorCaseDef(termName, caseDef.guard) match { - case q"scala.async.Async.await[${t}](${readed}.aread):${t1}" => - // here is 'reverse' of out read macros - acceptor.onRead(a,termName,readed,unwrappedType) - case q"gopher.goasync.AsyncWrapper.await[${t}](${readed}.aread):${t1}" => - acceptor.onRead(a,termName,readed,unwrappedType) - case q"scala.async.Async.await[${t}](${ch}.awrite($expression)):${t1}" => - acceptor.onWrite(a,termName,expression,ch,unwrappedType) - case q"gopher.goasync.AsyncWrapper.await[${t}](${ch}.awrite($expression)):${t1}" => - acceptor.onWrite(a,termName,expression,ch,unwrappedType) - case x@_ => - c.abort(tp.pos, "can't parse match guard: "+x); - } - } - } - } - caseDef.pat match { - case Bind(name,t) => - val termName = name.toTermName - t match { - case Typed(_,tp) => - acceptTypeTree(tp,termName) - //case Typed(_,tp) => - // c.abort(caseDef.pat.pos,s"x:channel.read or x:channel.write , tp must-br TypeTree, our tp=${showRaw(tp)}") - case _ => - c.abort(caseDef.pat.pos,s"x:channel.read or x:channel.write form is required, we have ${showRaw(caseDef.pat)}") - } - case Ident(n@TermName("_")) => acceptor.onIdle(a) // was - caseDef.pat - case Typed(Ident(name),tp) => acceptTypeTree(tp,name.toTermName) - case Typed(_,tp) => c.abort(caseDef.pat.pos,s"name in typed expression is expected, we have ${showRaw(caseDef.pat)}") - case _ => - c.abort(caseDef.pat.pos,s"bind in pattern is expected, we have ${showRaw(caseDef.pat)}") - } - } - - -} - -object SelectorBuilder -{ - - - def readingImpl[A,B:c.WeakTypeTag,S](c:Context)(ch:c.Expr[Input[A]])(f:c.Expr[A=>B]):c.Expr[S] = - { - import c.universe._ - f.tree match { - case Function(valdefs, body) => - val r = buildAsyncCall[B,S](c)(valdefs,body, - { (nvaldefs, nbody) => - q"""${c.prefix}.readingWithFlowTerminationAsync(${ch}, - ${Function(nvaldefs,nbody)} - ) - """ - }) - r - case _ => c.abort(c.enclosingPosition,"argument of reading.apply must be function") - } - } - - def writingImpl[A,T:c.WeakTypeTag,S](c:Context)(ch:c.Expr[Output[A]],x:c.Expr[A])(f:c.Expr[A=>T]):c.Expr[S] = - { - import c.universe._ - f.tree match { - case Function(valdefs, body) => - val retval = buildAsyncCall[T,S](c)(valdefs,body, - { (nvaldefs, nbody) => - q"""${c.prefix}.writingWithFlowTerminationAsync(${ch},${x}, - ${Function(nvaldefs,nbody)} - ) - """ - }) - retval - case _ => c.abort(c.enclosingPosition,"second argument of writing must have shape Function(x,y)") - } - } - - def transformDelayedMacroses[T:c.WeakTypeTag](c:Context)(block:c.Tree):c.Tree = - { - import c.universe._ - val transformer = new Transformer { - override def transform(tree:Tree): Tree = - tree match { - case Apply(TypeApply(Select(obj,TermName("implicitly")),List(objType)), args) => - // unresolve implicit references of specific type - if (!(obj.tpe eq null) && obj.tpe =:= typeOf[Predef.type] && - objType.tpe <:< typeOf[FlowTermination[Nothing]] - ) { - TypeApply(Select(obj,TermName("implicitly")),List(objType)) - } else { - super.transform(tree) - } - case Apply(TypeApply(Select(obj,member),objType), args) => - if (!(obj.tpe eq null) && obj.tpe =:= typeOf[CurrentFlowTermination.type] ) { - member match { - case TermName("exit") => - Apply(TypeApply(Select(obj,TermName("exitDelayed")),objType), args) - case _ => super.transform(tree) - } - } else { - super.transform(tree) - } - case Apply(Select(obj,member), args) => - if (!(obj.tpe eq null) && obj.tpe =:= typeOf[CurrentFlowTermination.type] ) { - member match { - case TermName("exit") => - Apply(Select(obj,TermName("exitDelayed")),args) - case _ => super.transform(tree) - } - } else { - super.transform(tree) - } - case _ => - super.transform(tree) - } - } - transformer.transform(block) - } - - def buildAsyncCall[T:c.WeakTypeTag,S](c:Context)( - valdefs: List[c.universe.ValDef], - body: c.Tree, - lastFun: (List[c.universe.ValDef], c.Tree) => c.Tree): c.Expr[S] = - { - import c.universe._ - val Seq(ft, ft1, ec, ec1) = Seq("ft","ft","ec","ec1") map (x => TermName(c.freshName(x))) - val ftParam = ValDef(Modifiers(Flag.PARAM),ft,tq"gopher.FlowTermination[${weakTypeOf[T]}]",EmptyTree) - val ecParam = ValDef(Modifiers(Flag.PARAM),ec,tq"scala.concurrent.ExecutionContext",EmptyTree) - val nvaldefs = ecParam::ftParam::valdefs - val asyncBody = GoAsync.transformAsyncBody[T](c)(body) - val nbody = q"""{ - implicit val ${ft1} = ${ft} - implicit val ${ec1} = ${ec} - gopher.goasync.AsyncWrapper.async(${transformDelayedMacroses[T](c)(asyncBody)})(${ec}) - } - """ - val newTree = lastFun(nvaldefs,nbody) - // untypecheck is necessory: otherwise exception in async internals - c.Expr[S](MacroUtil.cleanUntypecheck(c)(newTree)) - } - - def idleImpl[T:c.WeakTypeTag,S](c:Context)(body:c.Expr[T]):c.Expr[S] = - { - import c.universe._ - c.Expr[S](q"${c.prefix}.timeout(${c.prefix}.api.idleTimeout)(_ => ${body})") - } - - def timeoutImpl[T:c.WeakTypeTag,S](c:Context)(t:c.Expr[FiniteDuration])(f:c.Expr[FiniteDuration=>T]):c.Expr[S] = - { - import c.universe._ - f.tree match { - case Function(valdefs, body) => - val r = SelectorBuilder.buildAsyncCall[T,S](c)(valdefs,body, - { (nvaldefs, nbody) => - q"""${c.prefix}.timeoutWithFlowTerminationAsync(${t}, - ${Function(nvaldefs,nbody)} - ) - """ - }) - r - case _ => c.abort(c.enclosingPosition,"second argument of timeout must have shape Function(x,y)") - } - } - - - def handleErrorImpl[T:c.WeakTypeTag,S](c:Context)(f:c.Expr[Throwable=>T]):c.Expr[S]= - { - import c.universe._ - val contValDef = ValDef(Modifiers(Flag.PARAM), - TermName(c.freshName("cont")), - tq"gopher.channels.Continuated[${weakTypeOf[T]}]", - EmptyTree) - f.tree match { - case Function(valdefs, body) => - val r = SelectorBuilder.buildAsyncCall[T,S](c)(contValDef::valdefs,body,{ - (nvaldefs, nbody) => - q"""${c.prefix}.handleErrorWithFlowTerminationAsync(${Function(nvaldefs,nbody)})""" - }) - r - case _ => - c.abort(c.enclosingPosition, "second argument of error must have shape Function") - } - } - - -} - - - diff --git a/0.99.x/src/main/scala/gopher/channels/SelectorFactory.scala b/0.99.x/src/main/scala/gopher/channels/SelectorFactory.scala deleted file mode 100644 index 399e8ce9..00000000 --- a/0.99.x/src/main/scala/gopher/channels/SelectorFactory.scala +++ /dev/null @@ -1,100 +0,0 @@ -package gopher.channels - -import scala.language.experimental.macros -import scala.reflect.macros.whitebox.Context -import scala.reflect.api._ -import gopher._ -import gopher.util._ -import scala.concurrent._ -import scala.concurrent.duration._ -import scala.annotation.unchecked._ - - -/** - * Factory for select instantiation. - * Can be obtained via gopherAPI - * - * {{{ - * val selector = gopherApi.select.forever - * for(s <- selector) ... - * }}} - */ -class SelectFactory(val api: GopherAPI) -{ - - selectFactory => - - type timeout = FiniteDuration - - type error = Throwable - - trait SelectFactoryApi - { - def api = selectFactory.api - } - - /** - * forever builder. - *@see ForeverSelectorBuilder - */ - def forever: ForeverSelectorBuilder = new ForeverSelectorBuilder with SelectFactoryApi {} - - /** - * once builder, where case clause return type is `T` - */ - def once[T]: OnceSelectorBuilder[T] = new OnceSelectorBuilder[T] with SelectFactoryApi {} - - def inputBuilder[T]() = new InputSelectorBuilder[T](api) - - /** - * generic selector builder - */ - def loop[A]: SelectorBuilder[A] = new SelectorBuilder[A] with SelectFactoryApi {} - - /** - * afold - asynchronious version of fold. - * {{{ - * select.afold((0,1)) { (s,(x,y)) => - * s match { - * case x: out.write => (y,x+y) - * case q: quit.read => select.exit((x,y)) - * } - * } - * }}} - */ - def afold[S](s:S)(op:(S,Any)=>S):Future[S] = macro FoldSelectorBuilderImpl.afold[S] - - def fold[S](s:S)(op:(S,Any)=>S):S = macro FoldSelectorBuilderImpl.fold[S] - - def map[B](f:Any=>B):CloseableInput[B] = macro SelectorBuilderImpl.map[B] - - def input[B](f:PartialFunction[Any,B]):Input[B] = - macro SelectorBuilderImpl.input[B] - - def amap[B](f:PartialFunction[Any,B]):Input[B] = - macro SelectorBuilderImpl.input[B] - - // - - def exit[A](a:A):A = macro CurrentFlowTermination.exitImpl[A] - - def shutdown():Unit = macro CurrentFlowTermination.shutdownImpl - -} - -class ECSelectFactory(override val api:GopherAPI, val ec: ExecutionContext) extends SelectFactory(api) -{ - - thisECSelectFactory => - - trait EcProvider[A] extends SelectorBuilder[A] - { - override def ec: ExecutionContext = thisECSelectFactory.ec - } - - override def forever: ForeverSelectorBuilder = new ForeverSelectorBuilder with SelectFactoryApi with EcProvider[Unit] {} - - override def once[T]: OnceSelectorBuilder[T] = new OnceSelectorBuilder[T] with SelectFactoryApi with EcProvider[T] - - -} diff --git a/0.99.x/src/main/scala/gopher/channels/UnbufferedChannelActor.scala b/0.99.x/src/main/scala/gopher/channels/UnbufferedChannelActor.scala deleted file mode 100644 index 4f3d6db5..00000000 --- a/0.99.x/src/main/scala/gopher/channels/UnbufferedChannelActor.scala +++ /dev/null @@ -1,133 +0,0 @@ -package gopher.channels - -import akka.actor._ -import gopher._ - -import scala.concurrent._ -import scala.language._ -import scala.util.control.NonFatal - - -/** - * Actor backend for channel - */ -class UnbufferedChannelActor[A](id:Long, unused:Int, api: GopherAPI) extends ChannelActor[A](id,api) -{ - - protected[this] def onContWrite(cw:ContWrite[A,_]):Unit = - { - if (closed) { - cw.flowTermination.throwIfNotCompleted(new ChannelClosedException()) - } else if (!processReaders(cw)) { - writers = writers :+ cw - } - } - - - protected[this] def onContRead(cr:ContRead[A,_]):Unit = - { - if (closed) { - processReaderClosed(cr) - } else if (!processWriters(cr)) { - readers = readers :+ cr; - } - } - - protected[this] def getNElements():Int = 0 - - - def processReaders(w: ContWrite[A,_]) : Boolean = - { - var done = false - while(!(done || readers.isEmpty)) { - val current = readers.head - readers = readers.tail - done = processReader(current,w) - } - done - } - - protected[this] def processReader[B,C](reader:ContRead[A,B],writer:ContWrite[A,C]): Boolean = - reader.function(reader) match { - case Some(f1) => - writer.function(writer) match { - case Some((a,wcont)) => - Future{ - val cont = f1(ContRead.In value a) - api.continue(cont, reader.flowTermination) - }(api.gopherExecutionContext) - api.continue(wcont, writer.flowTermination) - true - case None => - val cont = f1(ContRead.Skip) - api.continue(cont, reader.flowTermination) - false - } - case None => - false - } - - def processWriters[C](reader:ContRead[A,C]): Boolean = - { - if (writers.isEmpty) { - false - } else { - val r = try { - reader.function(reader) - } catch { - case NonFatal(ex) => ex.printStackTrace() - throw ex - } - r match { - case Some(f1) => - var done = false - while(!writers.isEmpty && !done) { - val current = writers.head - writers = writers.tail - done = processWriter(current,f1,reader) - } - if (!done) { - f1(ContRead.Skip) - } - done - case None => - false - } - } - } - - protected[this] def processWriter[B,C](writer:ContWrite[A,B], - f1:ContRead.In[A]=>Future[Continuated[C]], - reader:ContRead[A,C]): Boolean = - writer.function(writer) match { - case Some((a,wcont)) => - Future { - val rcont = f1(ContRead.In value a) - api.continue(rcont,reader.flowTermination) - } - api.continue(wcont,writer.flowTermination) - true - case None => - false - } - - - def stopIfEmpty: Boolean = - { - require(closed==true) - stopReaders() - stopWriters() - doClose() - if (nRefs == 0) { - // here we leave 'closed' channels in actor-system untile they will be - // garbage-collected. TODO: think about actual stop ? - self ! GracefullChannelStop - } - true - } - - - private[this] implicit def ec: ExecutionContext = api.gopherExecutionContext - - -} diff --git a/0.99.x/src/main/scala/gopher/channels/ZippedInput.scala b/0.99.x/src/main/scala/gopher/channels/ZippedInput.scala deleted file mode 100644 index 622cb642..00000000 --- a/0.99.x/src/main/scala/gopher/channels/ZippedInput.scala +++ /dev/null @@ -1,116 +0,0 @@ -package gopher.channels - -import scala.concurrent._ -import scala.language.experimental.macros -import scala.reflect.macros.blackbox.Context -import scala.reflect.api._ -import scala.util._ -import java.util.concurrent.ConcurrentLinkedQueue -import gopher._ - - -class ZippedInput[A,B](override val api: GopherAPI, inputA: Input[A], inputB: Input[B]) extends Input[(A,B)] -{ - - import ZippedInput._ - - val pairs = new ConcurrentLinkedQueue[(A,B)]() - val readers = new ConcurrentLinkedQueue[ContRead[(A,B),_]] - - - def cbread[C](f: (ContRead[(A,B),C] => Option[ContRead.In[(A,B)] => Future[Continuated[C]]]), flwt: FlowTermination[C] ): Unit = - { - if (!pairs.isEmpty) { - implicit val ec = api.gopherExecutionContext - f(ContRead(f,this,flwt)) match { - case Some(f1) => - val ready = pairs.poll(); - val in = if (! (ready eq null) ) { - ContRead.Value(ready) - } else { - // unfortunelly, somebody has been eat our pair between !empty and poll() - ContRead.Skip - } - api.continue(f1(in), flwt) - case None => /* do nothing */ - } - } else { - readers.add(ContRead(f,this,flwt)) - val s = new State[A,B] - inputA.cbread[C](cont => Some(ContRead.liftIn(cont)(a => { - val toFire = s.synchronized{ - s.oa=Some(a) - s.ob.isDefined - } - fireAttempt(toFire, s) - } )) - , flwt) - inputB.cbread[C](cont => - Some(ContRead.liftIn(cont)(b => { - val toFire = s.synchronized{ - s.ob = Some(b) - s.oa.isDefined - } - fireAttempt(toFire,s) - } )) - , flwt) - } - - - def fireAttempt(toFire: Boolean, s:State[A,B]):Future[Continuated[C]] = - { - if (toFire) { - s match { - case State(Some(a),Some(b)) => - val pair = (a,b) - val cont = readers.poll().asInstanceOf[ContRead[(A,B),({type R})#R]] - // existencial type not allow cont.function(cont) - if (cont eq null) { - pairs.add(pair) - } else { - implicit val ec = api.gopherExecutionContext - cont.function(cont) match { - case Some(f1) => - api.continue(f1(ContRead.Value(pair)),cont.flowTermination) - case None => - pairs.add(pair) - } - } - case _ => throw new IllegalStateException("Impossible: fully-filled state is a precondition"); - } - } - // always return never, since real continuated we passed after f1 from readers queue was executed. - // note, that we can't return it direct here, becouse type of readers head continuation can be - // other than C, as in next scenario: - // 1. Reader R1 call cbread and start to collect (a1,b1) (readers <- R1) - // 2. Reader R2 call cbread and start to collect (a2,b2) (readers <- R2) - // 3. (a1,b1) collected, but R1 is locked. (pairs <- (a1,a2), readers -> drop R1) - // in such case fireAttempt for R1 will process R2 (wich can have different C in FlowTermination[C]) - Future successful Never - } - - - } - - -} - -object ZippedInput -{ - - // can't be case class: compiler error when annotating variables. - // see https://issues.scala-lang.org/browse/SI-8873 - class State[A,B] - { - @volatile var oa:Option[A] = None - @volatile var ob:Option[B] = None - } - - object State - { - def unapply[A,B](s:State[A,B]) = Some((s.oa,s.ob)) - } - -} - - diff --git a/0.99.x/src/main/scala/gopher/channels/package.scala b/0.99.x/src/main/scala/gopher/channels/package.scala deleted file mode 100644 index 6a11c4ba..00000000 --- a/0.99.x/src/main/scala/gopher/channels/package.scala +++ /dev/null @@ -1,27 +0,0 @@ - -package gopher - -/** - * - * == Overview == - * - *
    - *
  • [[Input]] and [[Output]] provide callback-based interfaces for input/output facilities.
  • - *
  • [[Channel]] provide implementation for asynchronics channels processing inside JVM.
  • - *
  • [[SelectorBuilder]] is a way to create selectors.
  • - *
- * - * - * == Internals == - * - * Core entity is [[Continuated]] which provide iteratee-like structure for reading and writing. - * Instance of [[Continuated]] represent one step of computations and leave in queue inside [[ChannelProcessor]] or [[ChannelActor]] - * [[Selector]] transform [[Continuated]] to executed exclusive with each other within one selector. Also we have [[IdleDetector]] which - * determinate idle selectors and activer appropriative actions. - * - */ -package object channels { - - -} - diff --git a/0.99.x/src/main/scala/gopher/goasync/AsyncApply.scala b/0.99.x/src/main/scala/gopher/goasync/AsyncApply.scala deleted file mode 100644 index 048214a7..00000000 --- a/0.99.x/src/main/scala/gopher/goasync/AsyncApply.scala +++ /dev/null @@ -1,146 +0,0 @@ -package gopher.goasync - -import scala.language.experimental.macros -import scala.language.reflectiveCalls -import scala.reflect.macros.blackbox.Context -import scala.reflect.api._ -import gopher._ -import gopher.util._ -import scala.concurrent._ -import scala.annotation.unchecked._ - - - -object AsyncApply -{ - - - def impl1[A:c.WeakTypeTag,B:c.WeakTypeTag,C:c.WeakTypeTag](c:Context)(hof:c.Expr[Function[Function[A,B],C]])(nf:c.Expr[Function[A,Future[B]]]): - c.Expr[Future[C]] = - { - import c.universe._ - val nhof = transformHof[A,B](c)(hof.tree,List()) - val retval = c.Expr[Future[C]](q"${nhof}(${nf})") - retval - } - - def apply1i[A,B,C](hof:Function[Function[A,B],C])(nf:Function[A,Future[B]],implicitParams:Any*):Future[C] = macro AsyncApply.impl1i[A,B,C] - - - def impl1i[A:c.WeakTypeTag,B:c.WeakTypeTag,C:c.WeakTypeTag](c:Context)(hof:c.Expr[Function[Function[A,B],C]])(nf:c.Expr[Function[A,Future[B]]],implicitParams:c.Expr[Any]*): c.Expr[Future[C]] = - { - import c.universe._ - val nhof = transformHof[A,B](c)(hof.tree,implicitParams.map(_.tree)) - val retval = (q"${nhof}(${nf})") - c.Expr[Future[C]](retval) - } - - def transformHof[A:c.WeakTypeTag,B:c.WeakTypeTag](c:Context)(hof:c.Tree,imps:Seq[c.Tree]):c.Tree = { - import c.universe.{Function=>_,_} - hof match { - case q"${p}.$h" => - val ah = genAsyncName(c)(h,hof.pos) - q"${p}.${ah}" - case q"${p}.$h[..$w]" => - val ah = genAsyncName(c)(h,hof.pos) - val r = q"${p}.${ah}[..$w]" - r - case q"($fp)=>$res($fp1)" if (fp.symbol == fp1.symbol) => - val nested = transformHof[A,B](c)(res,imps) - val (paramName, paramDef) = createAsyncParam[A,B](c)(fp) - val mfp2 = appendImplicitExecutionContext(c)(imps) - val transformed = q"($paramDef)=>$nested($paramName)(..$mfp2)" - c.typecheck(transformed) - case q"($fp)=>$res($fp1)(..$fp2)" if (fp.symbol == fp1.symbol) => - // ..fp2 is a list of implicit params. - val nested = transformHof[A,B](c)(res,imps) - val (paramName, paramDef) = createAsyncParam[A,B](c)(fp) - val mfp2 = appendImplicitExecutionContext(c)(fp2) - val r = q"($paramDef)=>$nested($paramName)(..$mfp2)" - r - case q"($fp)=>$res[$w1,$w2]($fp1)($fp2)" => - c.abort(hof.pos,"A1"+hof) - case q"($fp:$ft)=>$a" => - c.abort(hof.pos,"a="+a) - case q"{ ..$stats }" => - try { - val nstats = transformLast(c){ - t => transformHof[A,B](c)(t,imps) - }(stats) - q"{ ..$nstats }" - } catch { - case ex: Throwable => - System.err.println(s"error during transforming ${stats}") - ex.printStackTrace() - throw ex - } - case _ => c.abort(hof.pos,"hof match failed:"+hof+"\n raw:"+showRaw(hof)) - } - } - - def createAsyncParam[A:c.WeakTypeTag,B:c.WeakTypeTag](c:Context)(fp:c.Tree):(c.TermName,c.Tree) = - { - //TODO: add ability to change future system. - import c.universe._ - // TODO: check that fp is ident and get fp as name. - val nname = TermName(c.freshName()) - val paramType = tq"Function[${c.weakTypeOf[A]},scala.concurrent.Future[${c.weakTypeOf[B]}]]" - (nname,ValDef(Modifiers(Flag.PARAM),nname,paramType,EmptyTree)) - } - - def inferImplicitExecutionContext(c:Context)():c.Tree = - { - val ect = c.weakTypeOf[scala.concurrent.ExecutionContext] - c.inferImplicitValue(ect, silent=false) - } - - - def appendImplicitExecutionContext(c:Context)(paramList:Seq[c.Tree]):Seq[c.Tree] = - { - val t = inferImplicitExecutionContext(c)() - paramList.find(_.symbol == t.symbol) match { - case None => paramList :+ t - case Some(v) => paramList - } - } - -/* - def appendImplicitExecutionContextParam(c:Context)(paramList:List[c.Tree]):List[c.Tree]= - { - // check that paramList not contains ec. - // (note, input must be typed - paramList.find{ x => - x match { - case ValDef(m,pn,pt,pv) => - m.hasFlag(Flag.IMPLICIT) && pt =:= c.weakTypeOf[scala.concurrent.ExecutionContext] - case _ => false - } - } match { - case None => - val pName = TermName(c.freshName("ec")) - val pType = c.weakTypeOf[scala.concurrent.ExecutionContext] - ValDef(Modifiers(Flag.PARAM|Flag.IMPLICIT),pName,paramType,EmptyTree) - - } - } -*/ - - def genAsyncName(c:Context)(h:c.TermName,pos:c.Position):c.TermName = - { - import c.universe._ - h match { - case TermName(hname) => - TermName(hname+"Async") - case _ => - c.abort(pos,"ident expected for hight order function") - } - } - - def transformLast(c:Context)(f:c.Tree=>c.Tree)(block: List[c.Tree]):List[c.Tree] = - block match { - case Nil => Nil - case r::Nil => f(r)::Nil - case h::q => h::transformLast(c)(f)(q) - } - -} diff --git a/0.99.x/src/main/scala/gopher/goasync/AsyncIterable.scala b/0.99.x/src/main/scala/gopher/goasync/AsyncIterable.scala deleted file mode 100644 index 41ab8970..00000000 --- a/0.99.x/src/main/scala/gopher/goasync/AsyncIterable.scala +++ /dev/null @@ -1,64 +0,0 @@ -package gopher.goasync - -import scala.concurrent._ -import scala.async.Async._ -import scala.collection.BuildFrom -import scala.language.higherKinds - - -class AsyncIterable[A,CC[_] <: Iterable[_]](val x:CC[A]) //extends AnyVal [implementation restriction, [scala-2.11.8] -{ - - def foreachAsync[U](f: A => Future[U])(implicit ec:ExecutionContext): Future[Unit] = - async{ - val it: Iterator[A] = x.asInstanceOf[Iterable[A]].iterator - while(it.hasNext) { - val v:A = it.next() - await(f(v)) - } - } - - - def mapAsync[U](f: A => Future[U])(implicit ec:ExecutionContext): Future[CC[U]] = - async { - val builder = x.iterableFactory.newBuilder[U] // x.iterableFactory.newBuilder[U] - val it = x.asInstanceOf[Iterable[A]].iterator - while(it.hasNext) { - val v: A = it.next - builder += await(f(v)) - } - builder.result().asInstanceOf[CC[U]] - } - -} - - - -class AsyncIterableI[T](val x:Iterable[T]) //extends AnyVal [implementation restriction, [scala-2.11.8] -{ - - - def foreachAsync[U](f: T => Future[U])(implicit ec:ExecutionContext): Future[Unit] = - async{ - val it = x.iterator - while(it.hasNext) { - await(f(it.next)) - } - } - - def qq[U](f:T=>U): Unit = { - x.map(f) - } - - def mapAsync[F,U,Z](f: T => Future[U])(implicit bf: BuildFrom[F,U,Z], ec:ExecutionContext): Future[Z] = - async { - val builder = bf.newBuilder(x.asInstanceOf[F]) // x.iterableFactory.newBuilder[U] - val it = x.iterator - while(it.hasNext) { - val v = it.next - builder += await(f(v)) - } - builder.result() - } - -} diff --git a/0.99.x/src/main/scala/gopher/goasync/AsyncOption.scala b/0.99.x/src/main/scala/gopher/goasync/AsyncOption.scala deleted file mode 100644 index cffa207e..00000000 --- a/0.99.x/src/main/scala/gopher/goasync/AsyncOption.scala +++ /dev/null @@ -1,63 +0,0 @@ -package gopher.goasync - -import scala.concurrent._ -import scala.async.Async._ -import scala.collection.generic._ - - -class AsyncOption[T](val x:Option[T]) extends AnyVal -{ - - - def foreachAsync[U](f: T => Future[U])(implicit ec:ExecutionContext): Future[Unit] = - { - if (x.isDefined) { - f(x.get) map (_ => ()) - } else { - Future successful (()) - } - } - - - def mapAsync[U](f: T => Future[U])(implicit ec:ExecutionContext): Future[Option[U]] = - { - if (x.isDefined) { - f(x.get) map (x => Some(x)) - } else { - Future successful None - } - } - - def flatMapAsync[U](f: T => Future[Option[U]])(implicit ec:ExecutionContext): Future[Option[U]] = - { - if (x.isDefined) { - f(x.get) - } else { - Future successful None - } - } - - def filterAsync(f: T=>Future[Boolean])(implicit ec:ExecutionContext): Future[Option[T]] = - { - if (x.isDefined) { - f(x.get) map { r => - if (r) x else None - } - } else { - Future successful None - } - } - - def filterNotAsync(f: T=>Future[Boolean])(implicit ec:ExecutionContext): Future[Option[T]] = - { - if (x.isDefined) { - f(x.get) map { r => if (r) None else x } - } else { - Future successful None - } - } - - -} - - diff --git a/0.99.x/src/main/scala/gopher/goasync/AsyncWrapper.scala b/0.99.x/src/main/scala/gopher/goasync/AsyncWrapper.scala deleted file mode 100644 index ce9c2c11..00000000 --- a/0.99.x/src/main/scala/gopher/goasync/AsyncWrapper.scala +++ /dev/null @@ -1,35 +0,0 @@ -package gopher.goasync - -import scala.language.experimental.macros -import scala.reflect.macros.blackbox.Context -import scala.reflect.api._ -import scala.concurrent._ - -object AsyncWrapper -{ - - def async[T](x:T)(implicit ec:ExecutionContext):Future[T] = macro asyncImpl[T] - - def await[T](x:Future[T]):T = macro awaitImpl[T] - - def postWrap[T](x:T):T = macro postWrapImpl[T] - - def asyncImpl[T](c:Context)(x:c.Expr[T])(ec:c.Expr[ExecutionContext]):c.Expr[Future[T]] = - { - import c.universe._ - c.Expr[Future[T]](q"gopher.goasync.AsyncWrapper.postWrap(scala.async.Async.async(${x})(${ec}))") - } - - def awaitImpl[T](c:Context)(x:c.Expr[Future[T]]):c.Expr[T] = - { - import c.universe._ - c.Expr[T](q"gopher.goasync.AsyncWrapper.postWrap(scala.async.Async.await(${x}))") - } - - def postWrapImpl[T](c:Context)(x:c.Expr[T]):c.Expr[T]= - { - import c.universe._ - x - } - -} diff --git a/0.99.x/src/main/scala/gopher/goasync/GoAsync.scala b/0.99.x/src/main/scala/gopher/goasync/GoAsync.scala deleted file mode 100644 index 88a6c875..00000000 --- a/0.99.x/src/main/scala/gopher/goasync/GoAsync.scala +++ /dev/null @@ -1,287 +0,0 @@ -package gopher.goasync - -import scala.language.experimental.macros -import scala.language.reflectiveCalls -import scala.reflect.macros.blackbox.Context -import scala.reflect.api._ -import gopher._ -import gopher.util._ -import scala.concurrent._ -import scala.annotation.unchecked._ - - -/** - * async arround go. - * - * Basicly go is - * 1. translate await-like exressions inside inline functions to calls of appropriative async functions. - * (or show error if not found). - *``` - * x.foreach{ x => p; await(x); .. } - *``` - * become - *``` - * await( transform-defer( x.foreachAsync{ x => async(p; await(x); ..) }) ) - *``` - * (note, that channel.read macroses are expanded to await-s on this point) - * - * 2. transform defer calls if defer statement is found inside go: - *``` - * asnyc{ p .. defer(x) .. } - *``` - * become (reallity is a little complext, here is just idea) - *``` - * { val d = new Defers(); async{ p .. d.defer(x) .. }.onComplete(d.tryProcess) } - *``` - */ -object GoAsync -{ - - //TODO: add handling of try/catch and operations inside collections. - - def goImpl[T:c.WeakTypeTag](c:Context)(body:c.Expr[T])(ec:c.Expr[ExecutionContext]):c.Expr[Future[T]] = - { - import c.universe._ - val ttype = c.weakTypeOf[T] - val nbody = GoAsync.transformAsyncBody[T](c)(body.tree) - val asyncNBody = applyAsync[T](c)(nbody)(ec) - val r = if (containsDefer(c)(body)) { - val defers = TermName(c.freshName) - val promise = TermName(c.freshName) - // asyn transform wantstyped tree on entry, so we must substitute 'defers' to untyped - // values after it, no before. - q""" - gopher.goasync.GoAsync.transformDeferMacro[${ttype}]( - {implicit val ${defers} = new Defers[${c.weakTypeOf[T]}]() - val ${promise} = Promise[${c.weakTypeOf[T]}]() - ${asyncNBody}.onComplete( x => - ${promise}.complete(${defers}.tryProcess(x)) - )(${ec}) - ${promise}.future - } - ) - """ - } else { - q"${asyncNBody}" - } - c.Expr[Future[T]](r) - } - - def goScopeImpl[T:c.WeakTypeTag](c:Context)(body:c.Expr[T]):c.Expr[T] = - { - import c.universe._ - if (containsDefer(c)(body)) { - val nbody = transformDefer[T](c)(body.tree) - c.Expr[T](q"""{implicit val defered = new gopher.Defers[${c.weakTypeOf[T]}]() - defered.processResult(gopher.Defers.controlTry(${c.untypecheck(nbody)})) - }""") - } else { - body - } - } - - def containsDefer[T:c.WeakTypeTag](c:Context)(body:c.Expr[T]):Boolean = - { - import c.universe._ - val findDefer = new Traverser { - var found = false - override def traverse(tree:Tree):Unit = - { - if (!found) { - tree match { - case q"gopher.`package`.defer(..${args})" => found = true - case _ => super.traverse(tree) - } - } - } - } - findDefer traverse body.tree - findDefer.found - } - - def transformDeferMacro[T](body:Future[T]):Future[T] = macro transformDeferMacroImpl[T] - - def transformDeferMacroImpl[T:c.WeakTypeTag](c:Context)(body:c.Expr[Future[T]]):c.Expr[Future[T]] = - { - c.Expr[Future[T]](c.untypecheck(transformDefer[T](c)(body.tree))) - } - - def transformDefer[T:c.WeakTypeTag](c:Context)(body:c.Tree):c.Tree = - { - import c.universe._ - val transformer = new Transformer { - override def transform(tree:Tree):Tree = - tree match { - case q"gopher.`package`.defer(..${args})" => - val transformedArgs = args.map(x => Block(transform(x))) - q"implicitly[gopher.Defers[${weakTypeOf[T]}]].defer(..${transformedArgs} )" - case q"$gopher.`package`.recover[$tps](..${args})" => - val transformedArgs = args.map(x => transform(x)) - q"implicitly[gopher.Defers[${weakTypeOf[T]}]].recover(..${transformedArgs} )" - case _ => - super.transform(tree) - } - } - transformer.transform(body) - } - - def transformAsyncBody[T:c.WeakTypeTag](c:Context)(body:c.Tree):c.Tree = - { - import c.universe._ - var found = false - var transformed = false - val transformer = new Transformer { - override def transform(tree:Tree):Tree = - { - - // if subtree was transformed, try to transform again origin tree, - // because await can be lifted-up from previous level. - def transformAgainIfNested(tree: Tree):Tree = - { - val prevTransformed = transformed - transformed = false - val nested = super.transform(tree) - if (transformed) { - transform(nested) - }else{ - transformed = prevTransformed - nested - } - } - - tree match { - case q"${f1}(${a}=>${b})(..$a2)" => - // TODO: cache in tree. - found = findAwait(c)(b) - if (found) { - // this can be implicit parameters of inline apply. - // whe can distinguish first from second by looking at f1 shape. - val isTwoParams = if (!(tree.symbol eq null)) { - if (tree.symbol.isMethod) { - tree.symbol.asMethod.paramLists.size == 2 - } else if (tree.symbol eq NoSymbol) { - // untyped, hope for the best - true - } else false - } else true - if (isTwoParams) { - transformed = true - transformInlineHofCall1(c)(f1,a,b,a2) - } else { - super.transform(tree) - } - }else{ - // TODO: think, may-be try to transform first b instead nested [?] - transformAgainIfNested(tree) - } - case q"${f1}(${a}=>${b})" => - found = findAwait(c)(b) - if (found) { - transformed = true - transformInlineHofCall1(c)(f1,a,b,List()) - } else { - // TODO: think, may-be try to transform first b instead nested [?] - transformAgainIfNested(tree) - } - case _ => - super.transform(tree) - } - } - - } - - var r = transformer.transform(body) - - r - } - - // handle things like: - // q"${fun}(${param}=>${body})($implicitParams)" => - def transformInlineHofCall1(c:Context)(fun:c.Tree,param:c.Tree,body:c.Tree,implicitParams:List[c.Tree]):c.Tree = - { - import c.universe._ - val btype = body.tpe - val nb = body - val anb = atPos(body.pos){ - val nnb = transformAsyncBody(c)(nb) - val ec = c.inferImplicitValue(c.weakTypeOf[ExecutionContext]) - // untypechack is necessory, because async-transform corrupt - // symbols owners inside body and we change scope of param - // [scala-2.11.8] - MacroUtil.cleanUntypecheck(c)( - q"(${param})=>scala.async.Async.async[$btype](${nnb})($ec)" - ) - } - val ar = atPos(fun.pos) { - val uar = if (implicitParams.isEmpty) { - q"gopher.asyncApply1(${fun})(${anb})" - } else { - q"gopher.goasync.AsyncApply.apply1i(${fun})(${anb},${implicitParams})" - } - if (true) { - // typecheck is necessory - // 1. to prevent runnint analysis of async over internal awaits in anb as on - // enclosing async instead those applied from asyncApply - // 2. to expand macroses here, to prevent error during expanding macroses - // in next typecheck - c.typecheck(uar) - } else { - uar - } - } - //typecheck with macros disabled is needed for compiler, - //to set symbol 'await', because async macro discovered - //awaits by looking at symbols - val r = c.typecheck(q"scala.async.Async.await(${ar})",withMacrosDisabled=true) - r - } - - def findAwait(c:Context)(body:c.Tree): Boolean = - { - import c.universe._ - var found: Boolean = false - val transformer = new Transformer { - - override def transform(tree:Tree):Tree = - { - if (found) - tree - else { - tree match { - case q"(scala.async.Async.await[${w}]($r)):${w1}"=> - found = true - tree - case q"scala.async.Async.await[${w}]($r)"=> - found = true - tree - case q"(scala.async.Async.async[${w}]($r)):${w1}"=> - //TODO: add test to test-case - tree - case q"(${a}=>${b})" => - // don't touch nested functions - tree - //super.transform(tree) - case _ => - super.transform(tree) - } - } - } - - } - transformer.transform(body) - found - } - - - def applyAsync[T:c.WeakTypeTag](c:Context)(nbody:c.Tree)(ec:c.Expr[ExecutionContext]):c.Tree = - { - import c.universe._ - nbody match { - case q"scala.async.Async.await[$t](..${x})" => q"${x.head}" - case _ => q"scala.async.Async.async($nbody)($ec)" - } - - } - -} - diff --git a/0.99.x/src/main/scala/gopher/package.scala b/0.99.x/src/main/scala/gopher/package.scala deleted file mode 100644 index c6b56048..00000000 --- a/0.99.x/src/main/scala/gopher/package.scala +++ /dev/null @@ -1,198 +0,0 @@ - -import scala.language.experimental.macros -import scala.language.implicitConversions -import scala.language.higherKinds -import scala.concurrent._ -import gopher.channels._ -import gopher.goasync._ - -import scala.collection.BuildFrom - -/** - * Provides scala API for 'go-like' CSP channels. - * - * - * == Overview == - * - * see readme for quick introduction. - * - * == Usage == - * - * At first you must receive gopherApi as Akka extension: - *{{{ - * import gopher._ - * - * ..... - * val gopherApi = Gopher(actorSystem) - *}}} - * - * Then you can use CPS channels with blocling operations inside go clauses: - *{{{ - * val channel = gopherApi.makeChannel[Long] - * val n = 10000 - * val producer = go { - * @volatile var(x,y) = (0L,1L) - * for( s <- gopherApi.select.forever) { - * case z: channel.write if (z==x) => - * x = y - * y = x+z - * if (x > n) { - * channel.close - * implicitly[FlowTermination[Unit]].doExit() - * } - * } - * } - * val consumer = for((c,i) <- channel.zip(1 to n)) { - * Console.println(s"fib(\${i})=\${c}") - * } - * Await.ready(consumer, 10 seconds) - *}}} - * - * and defer/recover in go/goScope - * - *{{{ - * goScope{ - * val f = openFile(myFileName) - * defer{ - * if (! recover{case ex:FileNotFoundException => Console.println("invalid fname")}) { - * f.close() - * } - * } - * } - *}}} - * - *@see [[gopher.GopherAPI]] - *@see [[gopher.channels.Channel]] - *@see [[gopher.channels.Input]] - *@see [[gopher.channels.Output]] - *@see [[gopher.channels.SelectorBuilder]] - *@see [[gopher.channels.SelectFactory]] - *@author Ruslan Shevchenko - */ -package object gopher { - - - - // - // magnetic arguments for selector-builder unsugared API - // - - implicit def toAsyncFullReadSelectorArgument[A,B]( - f: ContRead[A,B] => Option[ContRead.In[A] => Future[Continuated[B]]] - ): ReadSelectorArgument[A,B] = AsyncFullReadSelectorArgument(f) - - implicit def toAsyncNoOptionReadSelectorArgument[A,B]( - f: ContRead[A,B] => (ContRead.In[A]=> Future[Continuated[B]]) - ): ReadSelectorArgument[A,B] = AsyncNoOptionReadSelectorArgument(f) - - implicit def toAsyncNoGenReadSelectorArgument[A,B]( - f: ContRead[A,B] => (A => Future[Continuated[B]]) - ): ReadSelectorArgument[A,B] = AsyncNoGenReadSelectorArgument(f) - - implicit def toAsyncPairReadSelectorArgument[A,B]( - f: (A, ContRead[A,B]) => Future[Continuated[B]] - ): ReadSelectorArgument[A,B] = AsyncPairReadSelectorArgument(f) - - implicit def toSyncReadSelectorArgument[A,B]( - f: ContRead[A,B] => (ContRead.In[A] => Continuated[B]) - ):ReadSelectorArgument[A,B] = SyncReadSelectorArgument(f) - - implicit def toSyncPairReadSelectorArgument[A,B]( - f: (A, ContRead[A,B]) => Continuated[B] - ):ReadSelectorArgument[A,B] = SyncPairReadSelectorArgument(f) - - - - implicit def toAsyncFullWriteSelectorArgument[A,B]( - f: ContWrite[A,B] => Option[(A,Future[Continuated[B]])] - ):WriteSelectorArgument[A,B] = AsyncFullWriteSelectorArgument(f) - - implicit def toAsyncNoOptWriteSelectorArgument[A,B]( - f: ContWrite[A,B] => (A,Future[Continuated[B]]) - ):WriteSelectorArgument[A,B] = AsyncNoOptWriteSelectorArgument(f) - - implicit def toSyncWriteSelectorArgument[A,B]( - f: ContWrite[A,B] => (A,Continuated[B]) - ): WriteSelectorArgument[A,B] = SyncWriteSelectorArgument(f) - - implicit def toAsyncFullSkipSelectorArgument[A]( - f: Skip[A] => Option[Future[Continuated[A]]] - ):SkipSelectorArgument[A] = AsyncFullSkipSelectorArgument(f) - - implicit def toAsyncNoOptSkipSelectorArgument[A]( - f: Skip[A] => Future[Continuated[A]] - ):SkipSelectorArgument[A] = AsyncNoOptSkipSelectorArgument(f) - - implicit def toSyncSelectorArgument[A]( - f: Skip[A] => Continuated[A] - ):SkipSelectorArgument[A] = SyncSelectorArgument(f) - -// -// Time from time we forgott to set 'go' in selector builder. -// Let's transform one automatically -// TODO: make 'go' nilpotent before this. -// -// implicit def toFuture[A](sb:SelectorBuilder[A]):Future[A] = sb.go - - //@scala.annotation.compileTimeOnly("FlowTermination methods must be used inside flow scopes (go, reading/writing/idle args)") - implicit def compileTimeFlowTermination[A]: FlowTermination[A] = ??? - - /** - * starts asyncronics execution of `body` in provided execution context. - * Inside go we can use `defer`/`recover` clauses and blocked read/write channel operations. - * - */ - def go[T](body: T)(implicit ec:ExecutionContext) : Future[T] = macro GoAsync.goImpl[T] - - /** - * provide access to using defer/recover inside body in the current thread of execution. - */ - def goScope[T](body: T): T = macro GoAsync.goScopeImpl[T] - - /** - * pseudostatement which can be used inside go/goScope block. - **/ - @scala.annotation.compileTimeOnly("defer/recover method usage outside go / goScope ") - def defer(x: =>Unit): Unit = ??? - - /** - * can be called only from defer block. If we in handling exception, try to apply f - * to exception and if it's applied - stop panic and return true, otherwise return false. - * - *@param f - partial function for recovering exception. - *@return true if exception was recovered, false otherwise - */ - @scala.annotation.compileTimeOnly("defer/recover method usage outside go / goScope ") - def recover[T](f: PartialFunction[Throwable, T]): Boolean = ??? - - /** - * sugar for reading value from future. - */ - implicit class FutureWithRead[T](f:Future[T]) - { - def read: T = macro InputMacro.read[T] - - def aread: Future[T] = f - } - - import scala.language.experimental.macros - import scala.reflect.macros.blackbox.Context - import scala.reflect.api._ - def awaitImpl[T](c:Context)(v:c.Expr[Future[T]]):c.Expr[T] = - { - import c.universe._ - c.Expr[T](q"scala.async.Async.await($v)") - } - - - def asyncApply1[A,B,C](hof:(A=>B)=>C)(nf:A=>Future[B]):Future[C] = - macro gopher.goasync.AsyncApply.impl1[A,B,C] - - implicit def toAsyncIterable[CC[_] <: Iterable[_],A](x:CC[A]): AsyncIterable[A,CC] = new AsyncIterable[A,CC](x) - //implicit def toAsyncIterableI[T](x:Iterable[T]): AsyncIterableI[T] = new AsyncIterableI[T](x) - implicit def toAsyncOption[T](x:Option[T]): AsyncOption[T] = new AsyncOption[T](x) - - - -} - diff --git a/0.99.x/src/main/scala/gopher/transputers/ReplicateTransputer.scala b/0.99.x/src/main/scala/gopher/transputers/ReplicateTransputer.scala deleted file mode 100644 index 6d3bdea3..00000000 --- a/0.99.x/src/main/scala/gopher/transputers/ReplicateTransputer.scala +++ /dev/null @@ -1,286 +0,0 @@ -package gopher.transputers - -import gopher._ -import gopher.channels._ -import gopher.util._ - -import scala.language.experimental.macros -import scala.reflect.macros.whitebox.Context -import scala.reflect.api._ -import scala.concurrent._ -import scala.annotation._ -import scala.language.higherKinds -import async.Async._ -import scala.collection.mutable.ArraySeq -import scala.reflect.ClassTag - -trait PortAdapter[P[_],A] -{ - def apply(x:P[A], n:Int, api: GopherAPI): (IndexedSeq[P[A]],Option[ForeverSelectorBuilder=>Unit]) -} - -class SharePortAdapter[P[_],A] extends PortAdapter[P,A] -{ - def apply(x:P[A], n:Int, api: GopherAPI): (IndexedSeq[P[A]],Option[ForeverSelectorBuilder=>Unit]) - = ((1 to n) map (_ => x) ,None) -} - -class DuplicatePortAdapter[A](buffLen: Int = 1) extends PortAdapter[Input,A] -{ - def apply(x:Input[A], n:Int, api: GopherAPI): (IndexedSeq[Input[A]],Option[ForeverSelectorBuilder=>Unit]) - = { - val upApi = api - val newPorts = (1 to n) map (_ => api.makeChannel[A](buffLen)) - def f(selector:ForeverSelectorBuilder): Unit = - selector.readingWithFlowTerminationAsync(x, - (ec:ExecutionContext, ft: FlowTermination[Unit], a: A) => async{ - var i = 0 - var fl:List[Future[A]]=Nil - while(iInt, buffLen: Int = 1) extends PortAdapter[Input,A] -{ - def apply(x:Input[A], n:Int, api: GopherAPI): (IndexedSeq[Input[A]],Option[ForeverSelectorBuilder=>Unit]) = - { - val newPorts = (1 to n) map (_ => api.makeChannel[A](buffLen)) - val sf: (ForeverSelectorBuilder=>Unit) = _.readingWithFlowTerminationAsync(x, - (ec:ExecutionContext, ft: FlowTermination[Unit], a: A) => - newPorts(f(a) % n).awrite(a).map(_ => ())(ec) - ) - (newPorts, Some(sf)) - } -} - - -class AggregatePortAdapter[A:ClassTag](f: Seq[A]=>A, buffLen:Int = 1) extends PortAdapter[Output,A] -{ - - def apply(x:Output[A], n:Int, api: GopherAPI): (IndexedSeq[Output[A]],Option[ForeverSelectorBuilder=>Unit]) = - { - val newPorts = (1 to n) map (_ => api.makeChannel[A](buffLen)) - val sf: (ForeverSelectorBuilder=>Unit) = _.readingWithFlowTerminationAsync(newPorts(0), - (ec:ExecutionContext, ft: FlowTermination[Unit], a: A) => async{ - val data = new Array[A](n) - data(0) = a - val i=1 - while(iReplicated[X] is transputer which keep n instances of X - * where ports of replicated consumer are connected to appropriative ports of instances in parallel. - * - *@see gopher.GopherAPI#replicate - */ -abstract class ReplicatedTransputer[T<:Transputer, Self](api: GopherAPI, n: Int) extends ParTransputer(api,List()) -{ - - thisReplicatedTransputer: Self => - - class InPortWithAdapter[A](in:Input[A]) extends InPort[A](in) - { - var adapter: PortAdapter[Input, A] = new SharePortAdapter[Input,A] - def owner: Self = thisReplicatedTransputer - } - - class OutPortWithAdapter[A](out:Output[A]) extends OutPort[A](out) - { - var adapter: PortAdapter[Output, A] = new SharePortAdapter[Output,A] - def owner: Self = thisReplicatedTransputer - } - - - class SelectorRunner(configFun: ForeverSelectorBuilder => Unit ) extends SelectTransputer - { - - selectorInit = ()=>configFun(this) - selectorInit() - - def api = thisReplicatedTransputer.api - def recoverFactory = () => new SelectorRunner(configFun) - } - - def init(): Unit - - - override def onStart():Unit= - { init() } - - override def onRestart(prev:Transputer):Unit= - { init() } - - - def replicated: Seq[T] - = replicatedInstances - - protected var replicatedInstances: Seq[T] = Seq() - - protected def replicatePorts():IndexedSeq[ForeverSelectorBuilder=>Unit] - - protected final def formChilds(selectorFuns:IndexedSeq[ForeverSelectorBuilder=>Unit]):Unit = { - childs = (selectorFuns map(new SelectorRunner(_))) ++ replicatedInstances - for(x <- childs) x.parent = Some(this) - } - -} - - - - -object PortAdapters -{ - - implicit class DistributeInput[G <: ReplicatedTransputer[_,_], A](in: G#InPortWithAdapter[A]) - { - def distribute(f: A=>Int): G = - { in.adapter = new DistributePortAdapter(f) - in.owner.asInstanceOf[G] - } - } - - - implicit class ShareInput[G <: ReplicatedTransputer[_,_],A](in: G#InPortWithAdapter[A]) - { - def share(): G = - { in.adapter = new SharePortAdapter[Input,A]() - in.owner.asInstanceOf[G] - } - } - - - implicit class ShareOutput[G <: ReplicatedTransputer[_,_], A](out: G#OutPortWithAdapter[A]) - { - def share(): G = - { out.adapter = new SharePortAdapter[Output,A] - out.owner.asInstanceOf[G] - } - } - - - implicit class DuplicateInput[G <: ReplicatedTransputer[_,_],A](in: G#InPortWithAdapter[A]) - { - def duplicate(): G = - { in.adapter = new DuplicatePortAdapter[A] - in.owner.asInstanceOf[G] - } - } - - - -} - - - -object Replicate -{ - - /** - * macro for GopherAPI.replicate - */ - def replicateImpl[T<:Transputer:c.WeakTypeTag](c:Context)(n:c.Expr[Int]):c.Expr[Transputer] = - { - import c.universe._ - - def portDefs[P:TypeTag](portWithAdapterName:String,portConstructorName:String):List[Tree] = - { - val portWithAdapterType = TypeName(portWithAdapterName) - val portConstructor = TermName(portConstructorName) - val ports = ReflectUtil.retrieveValSymbols[P](c.universe)(weakTypeOf[T]) - for(p <- ports) yield { - val getter = p.getter.asMethod - if (getter.returnType.typeArgs.length!=1) { - c.abort(p.pos, "assumed {In|Out}Port[A], have type ${getter.returnType} with typeArgs length other then 1") - } - val ta = getter.returnType.typeArgs.head - val name = TermName(getter.name.toString) - q"val ${name}: ${portWithAdapterType}[${ta}] = new ${portWithAdapterType}[${ta}](${portConstructor}().v)" - } - } - - def replicatePort(p:TermName):Tree= - q"""{ val (replicatedPorts,optSelectorFun) = ${p}.adapter(${p}.v,n,api) - for((r,e) <- (replicatedPorts zip replicatedInstances)) { - e.${p}.connect(r) - } - selectorFuns = selectorFuns ++ optSelectorFun - } - """ - - def retrieveValNames[P:TypeTag]:List[TermName] = - ReflectUtil.retrieveValSymbols[P](c.universe)(weakTypeOf[T]) map (x=>TermName(x.getter.name.toString)) - - def replicatePorts():List[Tree] = - { - (retrieveValNames[Transputer#InPort[_]] ++ retrieveValNames[Transputer#OutPort[_]]) map (replicatePort(_)) - } - - - val className = TypeName(c.freshName("Replicated"+weakTypeOf[T].typeSymbol.name)) - var stats = List[c.Tree]() ++ ( - portDefs[Transputer#InPort[_]]("InPortWithAdapter","InPort") - ++ - portDefs[Transputer#OutPort[_]]("OutPortWithAdapter","OutPort") - ) - - val retval = c.Expr[Transputer](q""" - { - class ${className}(api:GopherAPI,n:Int) extends ReplicatedTransputer[${weakTypeOf[T]},${className}](api,n) - { - type Self = ${className} - - def init(): Unit = - { - replicatedInstances = (1 to n) map (i => { - val x = api.makeTransputer[${weakTypeOf[T]}] - x.replicaNumber = i - x - }) - formChilds(replicatePorts) - } - - def replicatePorts():IndexedSeq[ForeverSelectorBuilder=>Unit] = - { - var selectorFuns = IndexedSeq[ForeverSelectorBuilder=>Unit]() - - ..${replicatePorts()} - - selectorFuns - } - - - ..${stats} - - } - new ${className}(${c.prefix},${n.tree}) - } - """ - ) - retval - } - - -} diff --git a/0.99.x/src/main/scala/gopher/transputers/TransputerSupervisor.scala b/0.99.x/src/main/scala/gopher/transputers/TransputerSupervisor.scala deleted file mode 100644 index 793d45c2..00000000 --- a/0.99.x/src/main/scala/gopher/transputers/TransputerSupervisor.scala +++ /dev/null @@ -1,86 +0,0 @@ -package gopher.transputers - -import gopher._ -import akka.actor._ -import scala.util._ - - -/** - * one actor, which perform operations for starting/stopping - **/ -class TransputerSupervisor(api: GopherAPI) extends Actor with ActorLogging -{ - import TransputerSupervisor._ - - implicit def ec = api.gopherExecutionContext - - def receive = { - case Start(t) => log.debug(s"starting ${t}") - t.goOnce onComplete { - case scala.util.Success(x) => - api.transputerSupervisorRef ! Stop(t) - case scala.util.Failure(ex) => - api.transputerSupervisorRef ! Failure(t,ex) - } - case Failure(t,ex) => - handleFailure(t,ex) - case Stop(t) => log.debug(s"${t} stopped") - if (!t.flowTermination.isCompleted) { - t.flowTermination.doExit(()) - } - case Escalate(t,ex) => - t.flowTermination.doThrow(ex) - } - - - def handleFailure(t: Transputer, ex: Throwable) = - { - import SupervisorStrategy.{Resume,Restart,Stop,Escalate} - if (t.recoveryStatistics.failure(ex,t.recoveryLimits,System.nanoTime)) { - escalate(t, new Transputer.TooManyFailures(t)) - } else if (t.recoveryFunction.isDefinedAt(ex)) { - t.recoveryFunction(ex) match { - case Resume => log.debug(s"${t} failed with ${ex.getMessage()}, resume execution") - log.debug("caused by {}",ex) - t.beforeResume() - self ! Start(t) - case Restart => log.debug(s"${t} failed with ${ex.getMessage()}, restart") - log.debug("caused by {}",ex) - val nt = t.recoverFactory() - nt.copyPorts(t) - nt.copyState(t) - nt.beforeRestart(t) - self ! Start(nt) - case Stop => self ! TransputerSupervisor.Stop(t) - case Escalate => log.debug(s"escalate from ${t} : ${ex}") - escalate(t,ex) - } - } else { - escalate(t,ex) - } - } - - def escalate(t: Transputer, ex: Throwable): Unit = - { - self ! Escalate(t, ex) - t.parent match { - case Some(p) => self ! Failure(p,ex) - case None => // root escalate, acccordint to akka rules: throw to supervisor of all system. - log.error(s"transputer exception escalated to root: ${ex.getMessage}") - throw ex; - } - } - -} - - -object TransputerSupervisor -{ - sealed trait Message - case class Start(t: Transputer) extends Message - case class Failure(t: Transputer,ex: Throwable) extends Message - case class Stop(t: Transputer) extends Message - case class Escalate(t: Transputer, ex: Throwable) extends Message -} - - diff --git a/0.99.x/src/main/scala/gopher/transputers/package.scala b/0.99.x/src/main/scala/gopher/transputers/package.scala deleted file mode 100644 index 72531352..00000000 --- a/0.99.x/src/main/scala/gopher/transputers/package.scala +++ /dev/null @@ -1,12 +0,0 @@ -package gopher - -/** - * transputers implementations - * - *@see gopher.transputers.TransputerSupervisor - *@see gopher.transputers.ReplicatedTransputer - **/ -package object transputers -{ - -} diff --git a/0.99.x/src/main/scala/gopher/util/ASTUtilImpl.scala b/0.99.x/src/main/scala/gopher/util/ASTUtilImpl.scala deleted file mode 100644 index 6d168a73..00000000 --- a/0.99.x/src/main/scala/gopher/util/ASTUtilImpl.scala +++ /dev/null @@ -1,21 +0,0 @@ -package gopher.util - -import scala.reflect.macros.blackbox.Context - - -trait ASTUtilImpl -{ - val c: Context - import c.universe._ - - def parseGuardInSelectorCaseDef(name: c.TermName, guard:c.Tree): c.Tree = - { - guard match { - case Apply(Select(Ident(`name`),TermName("$eq$eq")),List(expression)) => - expression - case _ => - c.abort(guard.pos, s"expected ${name}== in select guard") - } - } - -} diff --git a/0.99.x/src/main/scala/gopher/util/Effected.scala b/0.99.x/src/main/scala/gopher/util/Effected.scala deleted file mode 100644 index c8e1d4ad..00000000 --- a/0.99.x/src/main/scala/gopher/util/Effected.scala +++ /dev/null @@ -1,62 +0,0 @@ -package gopher.util - -import java.util.concurrent.atomic._ - -trait Effected[T] -{ - - def apply(f:T=>T): Unit - - @inline def <<=(f:T=>T): Unit = apply(f) - - def replace(x: T): Unit = apply(_ => x) - - @inline def :=(x:T): Unit = replace(x) - - protected def current: T -} - - -class SinglethreadedEffected[T](initValue:T) extends Effected[T] -{ - - override def apply(f: T=>T): Unit = - { v=f(v) } - - override def replace(x: T): Unit = - { v=x } - - override def current = v - - protected[this] var v = initValue -} - -class MultithreadedEffected[T](initValue:T) extends Effected[T] -{ - - override def apply(f: T=>T): Unit = - { - setv(f(v.get)) - } - - override def replace(x:T): Unit = - { - setv(x) - } - - protected[this] def setv(x: =>T):Unit = - { - var success = false; - while(!success) { - val prev = v.get() - val next = x - success = v.compareAndSet(prev,next) - } - } - - override def current = v.get - - protected[this] val v = new AtomicReference[T](initValue) -} - - diff --git a/0.99.x/src/main/scala/gopher/util/IntIndexedReverse.scala b/0.99.x/src/main/scala/gopher/util/IntIndexedReverse.scala deleted file mode 100644 index 5e1abe6e..00000000 --- a/0.99.x/src/main/scala/gopher/util/IntIndexedReverse.scala +++ /dev/null @@ -1,78 +0,0 @@ -package gopher.util - - - -import scala.collection.mutable -import scala.collection.mutable.ArrayBuffer - -case class CounterRecord[T](var value: T, var counter: Int = 0) - -class IntIndexedCounterReverse[T <: AnyRef](n:Int) { - - val values = new Array[CounterRecord[T]](n) - val backIndex = new mutable.WeakHashMap[T,CounterRecord[T]]() - - def put(i: Int, v:T):CounterRecord[T] = - { - var cr = values(i) - if ((cr eq null) || !(cr.value eq v)) { - backIndex.get(v) match { - case None => cr = new CounterRecord[T](v,0) - backIndex.put(v,cr) - case Some(backed) => cr = backed - } - values(i) = cr - } - cr - } - - def get(i: Int): Option[CounterRecord[T]] = - Option(values(i)) - - /** - * Search for index of v in T by reference. - * @param v - value to search - * @return index or -1 if not found - */ - def refIndexOf(v:T):Int = - { - var i=0 - var r = -1 - while(i < values.length && r == -1) { - if (values(i).value eq v) { - r = i - } - i += 1 - } - r - } - - def foreach(f:CounterRecord[T] => Unit): Unit = - { - var i=0 - while(iUnit):Unit = - { - var i = 0 - while(i < values.length) { - val current = values(i) - if (!(current eq null)) { - f(i,current) - } - i += 1 - } - } - - - def getBackIndex(v:T):Option[CounterRecord[T]] = - backIndex.get(v) - -} diff --git a/0.99.x/src/main/scala/gopher/util/MacroUtil.scala b/0.99.x/src/main/scala/gopher/util/MacroUtil.scala deleted file mode 100644 index 4a3350ce..00000000 --- a/0.99.x/src/main/scala/gopher/util/MacroUtil.scala +++ /dev/null @@ -1,138 +0,0 @@ -package gopher.util - -import scala.annotation._ -import scala.reflect.macros.blackbox.Context -import scala.reflect.api._ -import scala.language.reflectiveCalls - - -object MacroUtil -{ - - /** - * short representation of tree, suitable for show in - * error messages. - */ - def shortString(c:Context)(x:c.Tree):String = - { - val raw = c.universe.showRaw(x) - if (raw.length > SHORT_LEN) { - raw.substring(0,raw.length-3)+"..." - } else { - raw - } - } - - def skipAnnotation(c:Context)(x: c.Tree):c.Tree = - { - import c.universe._ - x match { - case Annotated(_,arg) => arg - case _ => x - } - } - - def hasAwait(c:Context)(x: c.Tree):Boolean = - { - import c.universe._ - val findAwait = new Traverser { - var found = false - override def traverse(tree:Tree):Unit = - { - if (!found) { - tree match { - case Apply(TypeApply(Select(obj,TermName("await")),objType), args) => - if (obj.tpe =:= typeOf[scala.async.Async.type]) { - found=true - } else super.traverse(tree) - case _ => super.traverse(tree) - } - } - } - } - findAwait.traverse(x) - findAwait.found - } - - - def unwrapOriginUnannotatedType(c:Context)(tp:c.Tree):c.Tree = - { - val tpoa = if (tp.isInstanceOf[c.universe.TypeTree]) { - val ttp = tp.asInstanceOf[c.universe.TypeTree] - if (ttp.original.isEmpty) ttp else ttp.original - } else tp - MacroUtil.skipAnnotation(c)(tpoa) - } - - - /** - * bug in async/scala-2.12.x - * async/types generate in state-machine next chunk of code: - *``` - * val result: scala.concurrent.Promise[Int] = Promise.apply[Int](); - * def result: scala.concurrent.Promise[Int] = stateMachine$macro$1041.this.result; - * val execContext: scala.concurrent.ExecutionContext = .. - * def execContext: scala.concurrent.Promise[Int] = stateMachine$macro$1041.this.execContext; - *``` - * when we attempt untype/type code again, it is not compiled. - *So, we need to remove result and execContext DefDefs - **/ - def removeAsyncStateMachineResultDefDef(c:Context)(tree: c.Tree):c.Tree = - { - import c.universe._ - - val outsideStm = new Transformer { - - override def transform(tree:Tree):Tree = - tree match { - case ClassDef(mods,name,tparams,impl) - if (name.toString.startsWith("stateMachine$")) => - impl match { - case Template(parents,self,body) => - ClassDef(mods,name,tparams, - Template(parents,self,removeResultDefDef(body,Nil))) - //case _ => // impossible, throw - } - case _ => super.transform(tree) - } - - @tailrec - def removeResultDefDef(body:List[Tree],acc:List[Tree]):List[Tree] = - { - body match { - case Nil => acc.reverse - case head::tail => - val (rest,nacc) = head match { - case DefDef(mods,name,tparams,vparamss,tpt,rsh) - if (name.toString == "result" || - name.toString == "execContext" ) => (tail,acc) - case _ => (tail, transform(head)::acc) - } - removeResultDefDef(rest,nacc) - } - } - - } - - val retval = outsideStm.transform(tree) - retval - } - - def cleanUntypecheck(c:Context)(tree:c.Tree):c.Tree = - { - if (isScala2_11) { - c.untypecheck(tree) - } else if (isScala2_12_0) { - removeAsyncStateMachineResultDefDef(c)(c.untypecheck(tree)) - } else { - c.untypecheck(tree) - } - } - - val isScala2_11 = util.Properties.versionNumberString.startsWith("2.11.") - - val isScala2_12_0 = util.Properties.versionNumberString.startsWith("2.12.0") - - - final val SHORT_LEN = 80 -} diff --git a/0.99.x/src/main/scala/gopher/util/ReflectUtil.scala b/0.99.x/src/main/scala/gopher/util/ReflectUtil.scala deleted file mode 100644 index bc8a185c..00000000 --- a/0.99.x/src/main/scala/gopher/util/ReflectUtil.scala +++ /dev/null @@ -1,59 +0,0 @@ -package gopher.util - -import scala.reflect._ -import scala.reflect.api._ - -object ReflectUtil -{ - - - def retrieveValSymbols[T:u.TypeTag](u:Universe)(ownerType:u.Type): List[u.TermSymbol] = - { - - // looks lile type comparison is broken in scala-2.13.3, let's make one themself - def varLeft(left: u.Type , right: u.Type): Boolean = { - right match { - case u.ExistentialType(rightParams, rightBase) => - left match { - case u.TypeRef(leftCn, leftSym, leftParams) => - rightBase match { - case u.TypeRef(rightCn, rightSym, rightParams) => - if (leftSym.info <:< rightSym.info) { - true - } else { - left <:< right - } - } - case _ => - left <:< right - } - case _ => - left <:< right - } - } - - val retval = ownerType.members.filter(_.isTerm).map(_.asTerm).filter{ x => - // isVar because of scala-2.13 bug: https://github.com/scala/bug/issues/11582 - if (x.isVal || x.isVar ) { - // in scala 2.12 getter method type, scala 2.11 - type - val r = x.typeSignature match { - case u.NullaryMethodType(rt) => - varLeft(rt, u.typeOf[T]) - case _ => - varLeft(x.typeSignature, u.typeOf[T]) - } - r - } else false - }.toList - retval - } - - - def retrieveVals[T:ru.TypeTag,O:ClassTag](ru:Universe)(mirror: ru.ReflectiveMirror, o:O): List[T] = - { - val im = mirror.reflect(o); - retrieveValSymbols(ru)(im.symbol.typeSignature) map (im.reflectField(_).get.asInstanceOf[T]) - } - - -} diff --git a/0.99.x/src/test/resources/application.conf b/0.99.x/src/test/resources/application.conf deleted file mode 100644 index 32d80156..00000000 --- a/0.99.x/src/test/resources/application.conf +++ /dev/null @@ -1,6 +0,0 @@ -akka { - //loglevel = DEBUG - //loglevel = INFO - loglevel = OFF -} - diff --git a/0.99.x/src/test/scala/example/BetterSieveSuite.scala b/0.99.x/src/test/scala/example/BetterSieveSuite.scala deleted file mode 100644 index 23a13c75..00000000 --- a/0.99.x/src/test/scala/example/BetterSieveSuite.scala +++ /dev/null @@ -1,64 +0,0 @@ -package example - -import gopher.channels.CommonTestObjects.gopherApi._ -import gopher.channels._ -import org.scalatest._ - -import scala.concurrent._ -import scala.language.postfixOps - -/** - * more 'scala-like' sieve - **/ -object BetterSieve -{ - import scala.concurrent.ExecutionContext.Implicits.global - - - def generate(n:Int, quit:Promise[Boolean]):Input[Int] = - { - val channel = makeChannel[Int]() - channel.awriteAll(2 to n) foreach (_ => quit success true) - channel - } - - /** - * flatFold modify channel with each read - */ - def filter(in:Input[Int]):Input[Int] = - in.flatFold{ (s,prime) => s.filter( _ % prime != 0) } - - def primes(n:Int, quit: Promise[Boolean]):Input[Int] = - filter(generate(n,quit)) - -} - -class BetterSieveSuite extends AsyncFunSuite -{ - - test("last prime before 1000") { - - val quit = Promise[Boolean]() - val quitInput = futureInput(quit.future) - - val pin = Sieve.primes(1000,quit) - - var lastPrime=0; - val future = select.forever { - case p: pin.read => - if (false) { - System.err.print(p) - System.err.print(" ") - } - lastPrime=p - case q: quitInput.read => - //System.err.println() - CurrentFlowTermination.exit(()); - } - future map { u => - assert(lastPrime == 997) - } - } - -} - diff --git a/0.99.x/src/test/scala/example/Bingo.scala b/0.99.x/src/test/scala/example/Bingo.scala deleted file mode 100644 index 136564ec..00000000 --- a/0.99.x/src/test/scala/example/Bingo.scala +++ /dev/null @@ -1,70 +0,0 @@ -package examples - -import akka.actor._ -import gopher._ -import gopher.channels._ -import org.scalatest._ - -import scala.language._ - - -trait Bingo extends SelectTransputer -{ - - val inX = InPort[Int]() - val inY = InPort[Int]() - val out = OutPort[Boolean]() - - loop { - case x: inX.read => - val y = inY.read - //Console.println(s"Bingo checker, received ${x}, ${y}") - out.write(x==y) - } - - recover { - case ex: ChannelClosedException => SupervisorStrategy.Stop - } - -} - -trait Acceptor extends SelectTransputer -{ - - val inA = InPort[Boolean]() - - var nBingos = 0 - var nPairs = 0 - - loop { - case x: inA.read => - // Console.println(s"acceptor: ${nPairs} ${nBingos} ${x}") - if (x) { - nBingos += 1 - } - nPairs += 1 - } - -} - -class BingoSuite extends AsyncFunSuite -{ - - test("bingo process wit identical input must return same") { - val inX = gopherApi.iterableInput(1 to 100) - val inY = gopherApi.iterableInput(1 to 100) - val bingo = gopherApi.makeTransputer[Bingo] - val acceptor = gopherApi.makeTransputer[Acceptor] - bingo.inX connect inX - bingo.inY connect inY - bingo.out >~~> acceptor.inA - val w = (bingo + acceptor).start() - w map { u => - assert(acceptor.nBingos == acceptor.nPairs) - } - } - - def gopherApi = CommonTestObjects.gopherApi - -} - diff --git a/0.99.x/src/test/scala/example/BroadcasterSuite.scala b/0.99.x/src/test/scala/example/BroadcasterSuite.scala deleted file mode 100644 index f8813080..00000000 --- a/0.99.x/src/test/scala/example/BroadcasterSuite.scala +++ /dev/null @@ -1,179 +0,0 @@ -package example.broadcast - -/** - * code from - * Concurrent Idioms #1: Broadcasting values in Go with linked channels. - * https://rogpeppe.wordpress.com/2009/12/01/concurrent-idioms-1-broadcasting-values-in-go-with-linked-channels/ - */ - -import java.util.concurrent.atomic.AtomicInteger - -import scala.concurrent.{Channel => _, _} -import scala.concurrent.duration._ -import scala.language.postfixOps -import scala.async.Async._ -import gopher._ -import gopher.channels._ -import CommonTestObjects.gopherApi._ -import gopher.tags.Now -import org.scalatest._ - - -class Broadcaster[A] -{ - import Broadcaster._ - import scala.concurrent.ExecutionContext.Implicits.global - - - val listenc: Channel[Channel[Channel[Message[A]]]] = makeChannel() - val sendc: Channel[A] = makeChannel() - val quitc: Channel[Boolean] = makeChannel() - - - val process = select.afold(makeChannel[Message[A]](1)) { (last,s) => - s match { - case v: sendc.read @unchecked => - val next = makeChannel[Message[A]](1) - last <~ ValueMessage(next,v) - next - case r: listenc.read @unchecked => - r <~ last - last - case q: quitc.read => - CurrentFlowTermination.exit(last) - } - } - - - - def alisten(): Future[Receiver[A]] = go { - val c = makeChannel[Channel[Message[A]]]() - listenc <~ c - new Receiver(c.read) - } - -} - - -object Broadcaster { - - import language.experimental.macros - import scala.reflect.macros.blackbox.Context - import scala.reflect.api._ - - class Receiver[A](initChannel: Channel[Message[A]]) - { - - val out = makeChannel[Option[A]]() - - select.afold(initChannel){ (ch,s) => - s match { - case b: ch.read => - ch.awrite(b) - b match { - case ValueMessage(nextChannel,v) => - out.write(Some(v)) - nextChannel - case EndMessage => - out.write(None) - //out.close() - select.exit(ch) - } - } - } - - def aread():Future[Option[A]] = out.aread - - def read():Option[A] = macro Receiver.readImpl[A] - - } - - object Receiver - { - def readImpl[A](c:Context)():c.Expr[Option[A]]= - { - import c.universe._ - awaitImpl[Option[A]](c)(c.Expr[Future[Option[A]]](q"${c.prefix}.aread()")) - } - } - - sealed trait Message[+A] - case class ValueMessage[A](ch: Channel[Message[A]],v:A) extends Message[A] - case object EndMessage extends Message[Nothing] - -} - - -class BroadcaseSuite extends AsyncFunSuite -{ - - def listen[A](r: Broadcaster.Receiver[A], out:Output[A]): Future[Unit] = go { - var finish = false; - while(!finish) { - val x = await(r.aread) - x match { - case Some(m) => out.write(m) - case None => finish = true - } - } - () - } - - def doBroadcast(out:Channel[Int]): Unit = go { - - val b = new Broadcaster[Int]() - - val nSend = new AtomicInteger(0) - val allDelivered = Promise[Int]() - def withIncrSend[A](a:A):A = - { - val n = nSend.incrementAndGet() - //wait - until we receive count([1,1,2,2,2]) - if (n==5) allDelivered success 5 - a - } - - val nout = out.premap(withIncrSend[Int]) - - val r1 = await(b.alisten()) - val l1 = listen(r1,nout) - val r2 = await(b.alisten()) - val l2 = listen(r2,nout) - - b.sendc.write(1) - - - val r3 = await(b.alisten()) - val l3 = listen(r3,nout) - - b.sendc.write(2) - - b.quitc.write(true) - - allDelivered.future.map(_ => out.close()) - //Thread.sleep(500) - //out.close() - } - - test("broadcast") { - val channel = makeChannel[Int]() - doBroadcast(channel) - val fsum = channel.afold(0){ (s,n) => - s+n - } - //val fsum = select.afold(0){ (s,sel) => - // sel match { - // case n:channel.read => s+n - // case _:channel.done => select.exit(s) - // } - //} - //val sum = Await.result(fsum,10 seconds) - fsum map { sum => - assert(sum == 8) - } - } - -} - - - diff --git a/0.99.x/src/test/scala/example/CopyFile.scala b/0.99.x/src/test/scala/example/CopyFile.scala deleted file mode 100644 index dd44aa4a..00000000 --- a/0.99.x/src/test/scala/example/CopyFile.scala +++ /dev/null @@ -1,25 +0,0 @@ -package example - -import java.io._ -import gopher._ - -object CopyFile { - - def main(args:Array[String]):Unit = - { - if (args.length < 3) { - System.err.println("usage: copy in out"); - } - copy(new File(args(1)), new File(args(2))) - } - - def copy(inf: File, outf: File): Long = - goScope { - val in = new FileInputStream(inf) - defer{ in.close() } - val out = new FileOutputStream(outf); - defer{ out.close() } - out.getChannel() transferFrom(in.getChannel(), 0, Long.MaxValue) - } - -} diff --git a/0.99.x/src/test/scala/example/FibonaccyAsyncSuite.scala b/0.99.x/src/test/scala/example/FibonaccyAsyncSuite.scala deleted file mode 100644 index 8f6679e3..00000000 --- a/0.99.x/src/test/scala/example/FibonaccyAsyncSuite.scala +++ /dev/null @@ -1,76 +0,0 @@ -package example - -import gopher._ -import gopher.channels._ -import org.scalatest._ - -import scala.concurrent.Future -import scala.language._ - - - -object FibonaccyAsync { - - import scala.concurrent.ExecutionContext.Implicits._ - - - def fibonacci(ch: Output[Long], quit: Input[Int]): Unit = { - var (x,y) = (0L,1L) - gopherApi.select.forever.writing(ch, y){ _ => - val z = x - x = y - y = z + y - }.reading(quit){ - x => - implicitly[FlowTermination[Unit]].doExit(()) - }.go - } - - def run(n:Int, acceptor: Long => Unit ): Future[Unit] = - { - val c = gopherApi.makeChannel[Long](1); - val quit = gopherApi.makeChannel[Int](1); - - var last=0L - /* - // error in compiler [scala-2.11.2] - //TODO: debug to small example and send pr - */ - /* - c.zip(1 to n).foreach{ a => - val (x,i) = a - //Console.print("%d, %d\n".format(i,x)) - last = x - } flatMap { x => quit.awrite(1) } - */ - val receiver = c.zip(1 to n).map{ case (x,i) => - // don't show, I trust you ;) - //Console.print("%d, %d\n".format(i,x)) - last = x - (i,x) - }.atake(n) flatMap { - x => - quit.awrite(1) - } - - fibonacci(c,quit) - - receiver.map{ _ => acceptor(last)} - - } - - lazy val gopherApi = channels.CommonTestObjects.gopherApi - -} - - -class FibonaccyAsyncSuite extends AsyncFunSuite -{ - - test("async fibonaccy must be processed up to 50") { - var last:Long = 0; - FibonaccyAsync.run(50, { last = _ } ) map(_ => assert(last != 0)) - } - -} - diff --git a/0.99.x/src/test/scala/example/FibonaccyAsyncUnsugaredSuite.scala b/0.99.x/src/test/scala/example/FibonaccyAsyncUnsugaredSuite.scala deleted file mode 100644 index 31b7dcc6..00000000 --- a/0.99.x/src/test/scala/example/FibonaccyAsyncUnsugaredSuite.scala +++ /dev/null @@ -1,81 +0,0 @@ -package example - -import gopher.channels._ -import org.scalatest._ - -import scala.async.Async._ -import scala.concurrent._ -import scala.language._ - -class FibonaccyAsyncUnsugaredSuite extends AsyncFunSuite { - - - object Fibonaccy { - - // illustrate usage of internal low-level API - // - def fibonacci(c: Output[Long], quit: Input[Int]): Future[Unit] = { - - @volatile var (x,y) = (0L,1L) - - val selector = new Selector[Unit](gopherApi) - - selector.addWriter(c, - ((cont:ContWrite[Long,Unit]) => Some{ - (x, async{ - val z=x - x=y - y=z+y - cont} - ) - } - ) - ) - selector.addReader(quit, - ((cont:ContRead[Int,Unit]) => Some{ (in:ContRead.In[Int]) => - Future successful Done((),cont.flowTermination) - } - ) - ) - selector.run - } - - def run(max:Int, acceptor: Long => Unit ): Future[Unit] = - { - val c = gopherApi.makeChannel[Long]() - val quit = gopherApi.makeChannel[Int]() - - val selector = new Selector[Long](gopherApi) - selector.addReader(c zip (1 to max), - (cont:ContRead[(Long,Int),Long]) => Some(ContRead.liftIn(cont){ in => - val (n,i) = in - //Console.println(s"received:${i}:${n} from channel ${cont.channel}") - Future successful { - if (i >= max) - Done(n,cont.flowTermination) - else - cont - } - }) - ) - val consumer = selector.run - - val producer = fibonacci(c,quit) - - consumer.map(x => acceptor(x)) - } - - - } - - test("fibonaccy must be processed up to 50") { - var last:Long = 0 - Fibonaccy.run(50, last = _ ) map (x => assert(last != 0)) - } - - val gopherApi = CommonTestObjects.gopherApi - -} - - - diff --git a/0.99.x/src/test/scala/example/FibonaccySuite.scala b/0.99.x/src/test/scala/example/FibonaccySuite.scala deleted file mode 100644 index 667330ed..00000000 --- a/0.99.x/src/test/scala/example/FibonaccySuite.scala +++ /dev/null @@ -1,61 +0,0 @@ -package example - -import gopher._ -import gopher.channels._ -import org.scalatest._ - -import scala.concurrent._ -import scala.language._ - -/* - * code from go tutorial: http://tour.golang.org/#66 -* -*/ - -object Fibonaccy { - - import scala.concurrent.ExecutionContext.Implicits.global - - def fibonacci(c: Output[Long], quit: Input[Int]): Future[Unit] = go { - var (x,y) = (0L,1L) - for(s <- gopherApi.select.forever) { - s match { - case z: c.write if (z == x) => - x = y - y = z+y - case q: quit.read => - implicitly[FlowTermination[Unit]].doExit(()) - } - } - } - - def run(n:Int, acceptor: Long => Unit ): Future[Unit] = - { - val c = gopherApi.makeChannel[Long](1); - val quit = gopherApi.makeChannel[Int](1); - val r = go { - // for loop in go with async inside - for( i<- 1 to n) { - val x: Long = (c ?) - //Console.println(s"received: ${i}, ${x}") - acceptor(x) - } - quit <~ 0 - } - - fibonacci(c,quit) - } - - def gopherApi = CommonTestObjects.gopherApi - -} - -class FibonaccySuite extends AsyncFunSuite -{ - - test("fibonaccy must be processed up to 50") { - var last:Long = 0; - Fibonaccy.run(50, last = _ ) map (x => assert(last!=0)) - } - -} diff --git a/0.99.x/src/test/scala/gopher/channels/ChannelCleanupSuite.scala b/0.99.x/src/test/scala/gopher/channels/ChannelCleanupSuite.scala deleted file mode 100644 index 563906aa..00000000 --- a/0.99.x/src/test/scala/gopher/channels/ChannelCleanupSuite.scala +++ /dev/null @@ -1,79 +0,0 @@ -package gopher.channels - - -import org.scalatest._ -import scala.concurrent.{Channel=>_,_} -import scala.concurrent.duration._ -import gopher._ -import gopher.tags._ -import java.lang.ref._ - - -import scala.async.Async._ -import scala.concurrent.ExecutionContext.Implicits.global - -object CleanupFlags -{ - @volatile var v1 = 0 -} - -class CleanedObject(val v: Int) -{ - override protected def finalize():Unit = - { - CleanupFlags.v1 = 1 - super.finalize() - } -} - -class ChannelCleanupSuite extends FunSuite -{ - - - // This test is run, but JVM ntot guarantie this. - // so, it can - test("unused channel-actor must be cleanuped during gc") { - - val cleanedObjectRq = new ReferenceQueue[CleanedObject](); - var weakRef: WeakReference[CleanedObject] = null; - - def createChannel(): Channel[CleanedObject] = - { - val channel = gopherApi.makeChannel[CleanedObject](100) - var obj = new CleanedObject(1) - weakRef = new WeakReference(obj, cleanedObjectRq) - val producer = channel.awrite(obj) - obj = null - channel - } - - var ch = createChannel() - ch = null; - var quit=false; - var nTryes = 0 - if (cleanedObjectRq.poll() == null) { - while(!quit) { - val x = (cleanedObjectRq.remove(100L) != null) - // when we have not null, object in channel is garbage collected - // this can be never done, when we have enought memory, so - // look at finalizer - quit=(CleanupFlags.v1 == 1) - System.gc(); - System.runFinalization() - Thread.sleep(100) - nTryes += 1 - //assert(nTryes < 100) - if (nTryes >= 100) { - cancel("Test to finalization is canceled, but it is not guarantued by JVM specs ") - } - } - } - - // System.err.println("CleanupFlags.v1="+CleanupFlags.v1) - - } - - - def gopherApi = CommonTestObjects.gopherApi - -} diff --git a/0.99.x/src/test/scala/gopher/channels/CommonTestObjects.scala b/0.99.x/src/test/scala/gopher/channels/CommonTestObjects.scala deleted file mode 100644 index ffa5147f..00000000 --- a/0.99.x/src/test/scala/gopher/channels/CommonTestObjects.scala +++ /dev/null @@ -1,30 +0,0 @@ -package gopher.channels - -import java.util.concurrent.TimeoutException - -import akka.actor._ -import gopher._ - -import scala.concurrent.{Future, Promise} -import scala.concurrent.duration.FiniteDuration - -object CommonTestObjects { - - lazy val actorSystem = ActorSystem.create("system") - lazy val gopherApi = Gopher(actorSystem) - - implicit class FutureWithTimeout[A](f: Future[A]) - { - import scala.concurrent.ExecutionContext.Implicits.global - def withTimeout(timeout:FiniteDuration):Future[A]= - { - val p = Promise[A]() - f.onComplete(p.tryComplete) - actorSystem.scheduler.scheduleOnce(timeout){ - p.tryFailure(new TimeoutException()) - } - p.future - } - } - -} diff --git a/0.99.x/src/test/scala/gopher/channels/SchedulerStartupTest.scala b/0.99.x/src/test/scala/gopher/channels/SchedulerStartupTest.scala deleted file mode 100644 index 9726f06f..00000000 --- a/0.99.x/src/test/scala/gopher/channels/SchedulerStartupTest.scala +++ /dev/null @@ -1,31 +0,0 @@ -package gopher.channels - -import org.scalatest._ -import gopher._ -import gopher.tags._ -import akka.actor._ -import scala.language._ -import scala.concurrent._ -import scala.concurrent.duration._ -import CommonTestObjects._ - -class SchedulerStartupTest extends FunSuite { - - - test("scheduler-allocated task mast start") { - - val scheduler = actorSystem.scheduler - val p = Promise[Int]() - //System.err.println("scheduler:"+scheduler) - val cancelable = scheduler.schedule( - 100 milliseconds, - 500 milliseconds - ){ - if (!p.isCompleted) p success 0 - }(ExecutionContext.Implicits.global) - val x = Await.result(p.future, 3000 milliseconds) - assert(x==0) - - } - -} diff --git a/0.99.x/src/test/scala/gopher/hofasyn/FibbonacyAsyncLoopSuite.scala b/0.99.x/src/test/scala/gopher/hofasyn/FibbonacyAsyncLoopSuite.scala deleted file mode 100644 index 6477158d..00000000 --- a/0.99.x/src/test/scala/gopher/hofasyn/FibbonacyAsyncLoopSuite.scala +++ /dev/null @@ -1,66 +0,0 @@ -package gopher.hofasyn - -import gopher._ -import gopher.channels._ -import scala.language._ -import scala.concurrent._ -import scala.concurrent.duration._ -import scala.async.Async._ - -import org.scalatest._ -import gopher.tags._ - - -/* -* code from go tutorial: http://tour.golang.org/#66 -* -*/ -object FibonaccyL { - - import scala.concurrent.ExecutionContext.Implicits.global - - def fibonacci(c: Output[Long], quit: Input[Int]): Future[Unit] = - go { - var (x,y) = (0L,1L) - for(s <- gopherApi.select.forever) { - s match { - case z: c.write if (z == x) => - x = y - y = z+y - case q: quit.read => - implicitly[FlowTermination[Unit]].doExit(()) - } - } - } - - def run(n:Int, acceptor: Long => Unit ): Future[Unit] = - { - val c = gopherApi.makeChannel[Long](1); - val quit = gopherApi.makeChannel[Int](1); - go { - for(i <-1 to n) { - val xLLFind = c.read - //Console.println(s"received: ${i}, ${xLLFind}") - acceptor(xLLFind) - } - //System.err.println("sending quit") - quit <~ 0 - } - - fibonacci(c,quit) - } - - def gopherApi = CommonTestObjects.gopherApi - -} - -class FibonaccyAsyncLoopSuite extends FunSuite -{ - - test("fibonaccy must be processed up to 50") { - @volatile var last:Long = 0; - Await.ready( FibonaccyL.run(50, last = _ ), 10 seconds ) - assert(last != 0) - } - -} diff --git a/0.99.x/src/test/scala/gopher/hofasyn/HofAsyncSuite.scala b/0.99.x/src/test/scala/gopher/hofasyn/HofAsyncSuite.scala deleted file mode 100644 index e02ebc24..00000000 --- a/0.99.x/src/test/scala/gopher/hofasyn/HofAsyncSuite.scala +++ /dev/null @@ -1,146 +0,0 @@ -package gopher.channels - -import gopher._ -import gopher.channels._ -import gopher.tags._ - -import org.scalatest._ - -import scala.language._ -import scala.concurrent.{Channel=>_,_} -import scala.concurrent.duration._ - -class HofAsyncSuite extends FunSuite -{ - - import scala.concurrent.ExecutionContext.Implicits.global - - - test("select emulation with macroses") { - - val channel = gopherApi.makeChannel[Int](100) - - go { - for( i <- 1 to 1000) - channel <~ i - } - - var sum = 0; - val consumer = go { - for(s <- gopherApi.select.forever) { - s match { - case i: channel.read => - //System.err.println("received:"+i) - sum = sum + i - if (i==1000) - implicitly[FlowTermination[Unit]].doExit(()) - } - } - sum - } - - Await.ready(consumer, 5.second) - - val xsum = (1 to 1000).sum - assert(xsum == sum) - - } - - - // Moved to new - test("test async operations inside map") { - val channel = gopherApi.makeChannel[Int](100) - channel.awriteAll(1 to 100) - val fresult = go{ - for(i <- 1 to 100) yield channel.read - } - val result = Await.result(fresult, 5.second) - assert(result(0)==1) - assert(result(1)==2) - assert(result(99)==100) - } - - - test("write to channel in options.foreach") { - val channel = gopherApi.makeChannel[Int](100) - val optChannel = Some(channel) - val f1 = go { - optChannel.foreach{ _.write(1) } - } - val f2 = go { - optChannel.map{ _.read } - } - val r2 = Await.result(f2, 5.second) - assert(r2.isDefined) - assert(r2 === Some(1) ) - } - - test("nested option foreach") { - val a:Option[Int] = Some(1) - val b:Option[Int] = Some(3) - val channel = gopherApi.makeChannel[Int](10) - val fin = go { - for (xa <- a; - xb <- b) channel.write(xa+xb) - } - val fout = channel.aread - val r = Await.result(fout, 5.second) - assert(r == 4) - } - - test("option flatMap") { - val channel = gopherApi.makeChannel[Int](10) - val map = Map(1->Map(2->channel)) - val fout = go { - for (x <- map.get(1); - ch <- x.get(2)) yield ch.read - } - val fin = channel.awrite(1) - val r = Await.result(fout, 5.second) - assert(r == Some(1)) - } - - test("channels foreach ") { - val channels = gopherApi.makeChannel[Channel[Int]](10) - val fin = go { - for(ch <- channels) { - ch.awrite(1) - } - } - val ch = gopherApi.makeChannel[Int](10) - channels.awrite(ch) - val fout = ch.aread - val r = Await.result(fout, 5.second) - assert(r == 1) - } - - test("lift inside select") { - import gopherApi._ - val ch1 = makeChannel[Int](10) - val ch2 = makeChannel[Int](10) - val quit = makeChannel[Boolean]() - val fin = go{ - for(s <- select.forever) - s match { - case x: ch1.read => - //System.err.println(s"received $x") - for(i <- 1 to x) { - //System.err.println(s"writing ${i*x}") - ch2.write(i*x) - } - case y: select.timeout if (y==(500.milliseconds)) => - System.err.println(s"timeout $y") - case z: quit.read => - select.exit(()) - } - } - val fout = ch1.awrite(2) - val x1 = ch2.aread - val rx1 = Await.result(x1, 1 minute) - quit.awrite(true) - assert(rx1 == 2) - } - - lazy val gopherApi = CommonTestObjects.gopherApi - -} diff --git a/0.99.x/src/test/scala/gopher/internal/FoldParseSuite.scala b/0.99.x/src/test/scala/gopher/internal/FoldParseSuite.scala deleted file mode 100644 index 26d38409..00000000 --- a/0.99.x/src/test/scala/gopher/internal/FoldParseSuite.scala +++ /dev/null @@ -1,85 +0,0 @@ -package gopher.internal - -import gopher._ -import gopher.channels._ -import scala.language._ -import scala.concurrent._ -import scala.concurrent.duration._ - -import org.scalatest._ -import gopher.tags._ - - -object FoldData { - - import scala.concurrent.ExecutionContext.Implicits.global - - def foldWithCase(c: Output[Long], quit: Input[Int]): Future[(Long,Long,Long)] = - gopherApi.select.afold((0L,1L,2L)) { - case ((x,y,z), s) => s match { - case v: c.write if (v==x) => - (y,z,y+z) - case q: quit.read => - CurrentFlowTermination.exit((x,y,z)) - } - } - - def foldWithCaseWithoutGuard(c: Output[Long], quit: Input[Int]): Future[(Long,Long,Long)] = - gopherApi.select.afold((0L,1L,2L)) { - case ((x,y,z), s) => s match { - case x: c.write => - (y,z,y+z) - case q: quit.read => - CurrentFlowTermination.exit((x,y,z)) - } - } - - - def foldWithoutCase(c: Output[Long], quit: Input[Int]): Future[Long] = - gopherApi.select.afold(1L) { (x,s) => - s match { - case v: c.write if (v==x) => x+1 - case q: quit.read => CurrentFlowTermination.exit(x) - } - } - - - def run1(n:Int, acceptor: Long => Unit ): Future[(Long,Long,Long)] = - { - val c = gopherApi.makeChannel[Long](1); - val quit = gopherApi.makeChannel[Int](1); - val r = go { - // for loop in go with async insied yet not supported - var i = 1 - while(i <= n) { - val x: Long = (c ?) - //Console.println(s"received: ${i}, ${x}") - acceptor(x) - i += 1 - } - quit <~ 0 - } - - foldWithCase(c,quit) - - - } - - - def gopherApi = CommonTestObjects.gopherApi - -} - -class FoldParseSuite extends FunSuite -{ - - test("fold must be parsed") { - @volatile var last:Long = 0; - Await.ready( FoldData.run1(50, last = _ ), 10 seconds ) - assert(last != 0) - } - - test("case var must shadow text") { - } - -} diff --git a/0.99.x/src/test/scala/gopher/obsolete/IOComposeSuite.scala b/0.99.x/src/test/scala/gopher/obsolete/IOComposeSuite.scala deleted file mode 100644 index 569bd27f..00000000 --- a/0.99.x/src/test/scala/gopher/obsolete/IOComposeSuite.scala +++ /dev/null @@ -1,75 +0,0 @@ -package gopher.channels - -import gopher._ -import munit._ - -class IOComposeSuite extends FunSuite { - - - test("simple composition of IO with map") { - import gopherApi._ - - val ch = makeChannel[Int]() - val ch1 = makeChannel[Int]() - val chs = ch1.map( _.toString ) - - val pipeline = ch |> chs - - pipeline.awriteAll(List(10,12,34,43)) - - for{ - r1 <- pipeline.aread - r2 <- pipeline.aread - r3 <- pipeline.aread - r4 <- pipeline.aread - } yield assert((r1,r2,r3,r4) == ("10","12","34","43") ) - - } - - - test("simple double composition of IO with map") { - import gopherApi._ - - val ch = makeChannel[Int]() - val chs = makeChannel[Int]().map( _.toString ) - val reverse = makeChannel[String]().map(_.reverse.toInt) - - val pipeline = ch |> chs |> reverse - - pipeline.awriteAll(List(10,12,34,43)) - - for{ - r1 <- pipeline.aread - r2 <- pipeline.aread - r3 <- pipeline.aread - r4 <- pipeline.aread - } yield assert((r1,r2,r3,r4) == (1,21,43,34) ) - - } - - test("closing channel must close it's composition") { - import gopherApi._ - - //pending - - val ch = makeChannel[Int]() - val ch1 = makeChannel[Int]() - - val composed = ch |> ch1 - - val f1 = for{ - _ <- ch.awrite(1) - } yield ch.close() - - for{ - x1 <- composed.aread - _ = assert(x1==1) - x2 <- recoverToSucceededIf[ChannelClosedException](composed.aread) - } yield x2 - - } - - - val gopherApi = CommonTestObjects.gopherApi - -} diff --git a/0.99.x/src/test/scala/gopher/scope/DefersSuite.scala b/0.99.x/src/test/scala/gopher/scope/DefersSuite.scala deleted file mode 100644 index af956d5c..00000000 --- a/0.99.x/src/test/scala/gopher/scope/DefersSuite.scala +++ /dev/null @@ -1,85 +0,0 @@ -package gopher.scope - -import scala.annotation.tailrec -import scala.util._ -import scala.reflect.runtime.universe.{Try => _, _} -import scala.io._ -import gopher._ - -import org.scalatest._ - -trait Source -{ - def name(): String - def lines(): Iterator[String] - def close(): Unit -} - -object TestParser -{ - - def parseCsv(source: Source): Either[String, Seq[Seq[Double]]] = - withDefer[Either[String,Seq[Seq[Double]]]]{ d => - d.defer{ - if (!d.recover { - case ex: Throwable => Left(ex.getMessage) - }) - source.close() - } - val retval:Either[String,Seq[Seq[Double]]] = Right{ - for( (line, nLine) <- source.lines.toList zip Stream.from(1) ) yield withDefer[Seq[Double]] { d => - line.split(",") map { s=> - d.defer{ - d.recover{ - case ex: NumberFormatException => - throw new RuntimeException(s"parse error in line ${nLine} file ${source.name} ") - } - } - s.toDouble - } - }.toSeq - } - retval - } - -} - - -class DefersSuite extends FunSuite -{ - - test("Defers.parseCsv: reading from unexistent file will return failure with FileNotFoundException") { - - val s = new Source { - def name()="unexistent.txt" - def lines()=source.getLines - def close()=source.close() - lazy val source = scala.io.Source.fromFile(name) - } - TestParser.parseCsv(s) match { - case Right(x) => assert(false,"unexistent source parsed") - case Left(s) => assert(s.contains("file")) - } - - } - - test("Defers.parseCsv: error in second string must be discovered") { - val s = new Source { - def name()="internal" - def lines()=Seq( - "1,3,4,5,6.0,8,9", - "3,4,5,6,xxxx7.0,8,9" - ).iterator - def close(): Unit = {} - } - - TestParser.parseCsv(s) match { - case Right(x) => assert(false,"source with error parsed") - case Left(s) => assert(s.contains("2")) - } - - } - - -} - diff --git a/0.99.x/src/test/scala/gopher/scope/GoWithDeferSuite.scala b/0.99.x/src/test/scala/gopher/scope/GoWithDeferSuite.scala deleted file mode 100644 index 9cd17867..00000000 --- a/0.99.x/src/test/scala/gopher/scope/GoWithDeferSuite.scala +++ /dev/null @@ -1,50 +0,0 @@ -package gopher.scope - - -import org.scalatest.FunSuite -import gopher._ -import gopher.tags._ - -import scala.language._ -import scala.concurrent._ -import scala.concurrent.duration._ -import scala.concurrent.ExecutionContext.Implicits.global - -class GoWithDeferSuite extends FunSuite { - - - test("2.1. goWithDefer: simple statement with defer must be processed") { - @volatile var x = 0 - val f = go { - defer( {x = 2} ) - x = 1 - } - Await.ready(f, 1 second) - assert(x === 2) - } - - test("2.2. typed go with recover") { - var x = 0 - val s = go{ defer{ recover{case ex: Throwable => "CCC"} } ; throw new RuntimeException("AA-go"); "4" } - Await.ready(s, 1 second) - assert(Await.result(s, 1 second)=="CCC") - } - - test("2.2. go with defer and while") { - var x = 0; - var f:Future[Unit] = go { - defer{ x=3; } - var n=4; - while(n > 0) { - n = n-1; - } - } - Await.ready(f, 1 second) - assert(x === 3) - } - - // TODO: go with select. - -} - - diff --git a/0.99.x/src/test/scala/gopher/scope/ScopeMacroSuite.scala b/0.99.x/src/test/scala/gopher/scope/ScopeMacroSuite.scala deleted file mode 100644 index c35f43fb..00000000 --- a/0.99.x/src/test/scala/gopher/scope/ScopeMacroSuite.scala +++ /dev/null @@ -1,71 +0,0 @@ -package gopher.scope - - -import org.scalatest.FunSuite -import gopher._ -import gopher.tags._ - - - -class ScopeMacroSuite extends FunSuite { - - - test("1. goScope: simple statement with defer must be processed") { - var x = 0 - goScope { - defer{ x = 2 } - x = 1 - } - assert(x === 2) - } - - test("2. typed goScope") { - var x = 0 - val s = goScope{ defer{ recover{case ex: Throwable => "CCC"} } ; throw new RuntimeException("AA"); "4" } - assert(s=="CCC") - } - - test("3. defered code must be called when non-local return") { - - var deferCalled = false - - def testFun(): Int = - goScope { - defer{ - deferCalled=true - } - if (true) { - return 34; - } - 42 - } - - val q = testFun() - - assert(q==34) - assert(deferCalled) - - } - - test("4. non-local return must not be catched in recover") { - - var thCatched = false - - def testFun(): Int = - goScope { - defer{ recover{ case th: Throwable => { thCatched=true; 5 } } } - if (true) { - return 10; - } - 11 - } - - val q = testFun() - assert(q==10) - assert(!thCatched) - - } - -} - - diff --git a/0.99.x/src/test/scala/gopher/scope/SimpleStatementSuite.scala b/0.99.x/src/test/scala/gopher/scope/SimpleStatementSuite.scala deleted file mode 100644 index 627d4ce3..00000000 --- a/0.99.x/src/test/scala/gopher/scope/SimpleStatementSuite.scala +++ /dev/null @@ -1,57 +0,0 @@ -package gopher.scope - -import org.scalatest.FunSuite -import gopher._ -import gopher.tags._ - - -class SimpleStatementSuite extends FunSuite -{ - - test("withDefer: simple statement without defer must be processed") { - var x = 0 - withDefer[Unit] { d=> - x = 1 - } - assert(x === 1) - } - - test("withDefers: simple statement with defer must be processed") { - var x = 0 - withDefer[Unit] { d => - d.defer{ x = 2 } - x = 1 - } - assert(x === 2) - } - - test("withDefers: simple statement with panic must be processed") { - var x = 0 - withDefer[Unit] { d=> - d.defer { - d.recover{ - case ex: Throwable => - x=1 - } - } - if (x==0) { - throw new IllegalStateException("x==0"); - } - } - assert(x==1) - } - - test("withDefers: recover must resturn value") { - val x = withDefer[Int] { d=> - val retval = 3; - d.defer { d.recover { - case ex: IllegalStateException => 5 - }} - throw new IllegalStateException("Be-Be-Be") - retval - } - assert(x==5) - } - -} - diff --git a/0.99.x/src/test/scala/gopher/tags/Gen.scala b/0.99.x/src/test/scala/gopher/tags/Gen.scala deleted file mode 100644 index 2eb9692d..00000000 --- a/0.99.x/src/test/scala/gopher/tags/Gen.scala +++ /dev/null @@ -1,5 +0,0 @@ -package gopher.tags - -import org.scalatest._ - -object Gen extends Tag("Gen") diff --git a/0.99.x/src/test/scala/gopher/tags/Now.scala b/0.99.x/src/test/scala/gopher/tags/Now.scala deleted file mode 100644 index 14250192..00000000 --- a/0.99.x/src/test/scala/gopher/tags/Now.scala +++ /dev/null @@ -1,6 +0,0 @@ -package gopher.tags - -import org.scalatest.Tag - -object Now extends Tag("Now") - diff --git a/0.99.x/src/test/scala/gopher/time/TimeSuite.scala b/0.99.x/src/test/scala/gopher/time/TimeSuite.scala deleted file mode 100644 index 2466c78b..00000000 --- a/0.99.x/src/test/scala/gopher/time/TimeSuite.scala +++ /dev/null @@ -1,29 +0,0 @@ -package gopher.time - -import java.time.Instant - -import org.scalatest.AsyncFunSuite - -import scala.concurrent.duration._ -import scala.language.postfixOps - -/** - * TimeSuite for gopher time API - */ -class TimeSuite extends AsyncFunSuite { - - import gopher.channels.CommonTestObjects._ - - test("ticker must prodice expired time") { - val accuracy = 50 - val ticker = gopherApi.time.newTicker(100 milliseconds) - val startTime = Instant.now() - for { _ <- gopherApi.time.asleep(300 milliseconds) - r <- ticker.atake(2) - } yield { - assert(r(0).toEpochMilli - startTime.toEpochMilli > 200) - assert(r(1).toEpochMilli - r(0).toEpochMilli >= 100-accuracy) - } - } - -} diff --git a/0.99.x/src/test/scala/gopher/transputers/ReplicateSuite.scala b/0.99.x/src/test/scala/gopher/transputers/ReplicateSuite.scala deleted file mode 100644 index ce67c0e9..00000000 --- a/0.99.x/src/test/scala/gopher/transputers/ReplicateSuite.scala +++ /dev/null @@ -1,182 +0,0 @@ -package gopher.transputers - -import scala.language._ -import gopher._ -import gopher.channels._ -import gopher.util._ -import gopher.tags._ -import org.scalatest._ -import scala.concurrent._ -import scala.concurrent.duration._ -import akka.actor._ - -sealed trait ControlMessage -case class SetMaxWords(n:Int) extends ControlMessage -case class SetMaxUsers(n:Int) extends ControlMessage -case class SendTopWords(userId: Long, nWords:Int) extends ControlMessage -case object Clear extends ControlMessage -case object Stop extends ControlMessage - -sealed trait OverflowMessage -case object UsersOverflow extends OverflowMessage -case class WordsOverflow(userId: Long) extends OverflowMessage - -trait WordCountTestTransputer extends SelectTransputer -{ - - val inS = InPort[(Long,String)]() - val control = InPort[ControlMessage]() - - val topOut = OutPort[(Long,Seq[(String,Int)])]() - val overflows = OutPort[OverflowMessage]() - - var data = Map[Long,Map[String,Int]]() - var maxWords : Int = 100 - var maxUsers : Int = 100 - - loop { - case x : inS.read @unchecked => - val (id, word) = x - val nWords = updateData(id, word ) - if (data.size > maxUsers) { - overflows.write(UsersOverflow) - } - if (nWords > maxWords) { - overflows.write(WordsOverflow(id)) - } - case c: control.read => - c match { - case SetMaxWords(n) => maxWords=n - case SetMaxUsers(n) => maxUsers=n - case SendTopWords(userId, nWords) => - topOut.write((userId,topNWords(userId, nWords))) - case Clear => data = Map() - case Stop => stop() - } - - } - - def updateData(userId: Long, word: String): Int = - data.get(userId) match { - case Some(m) => val newM = updateWordCount(m,word) - data = data.updated(userId, newM) - newM.size - case None => data = data.updated(userId,Map(word -> 1)) - 1 - } - - def updateWordCount(m:Map[String,Int],w:String): Map[String,Int] = - m.updated(w, - m.get(w) match { - case Some(n) => n+1 - case None => 1 - } - ) - - def topNWords(userId:Long, nWords: Int): Seq[(String,Int)] = - data.get(userId) match { - case Some(m) => m.toSeq.sortBy{ case (w1,n1) => -n1 }.take(nWords) - case None => List() - } - -} - -trait TestDupper extends SelectTransputer with TransputerLogging -{ - - val in = InPort[Int]() - - val out = OutPort[Int]() - - @volatile var nProcessedMessages = 0 - - loop { - case x: in.read => - log.info(s"testDupper, replica: ${replica} received ${x} from ${in}") - // TODO: implement gopherApi.time.wait - Thread.sleep(1000) - out.write(x) - nProcessedMessages += 1 - } - -} - - - -class ReplicateSuite extends FunSuite -{ - - test(" define replication of TestDupper with port adapters") { - val r = gopherApi.replicate[TestDupper](10) - import PortAdapters._ - ( r.in.distribute( (_ % 37 ) ). - out.share() - ) - val inChannel = gopherApi.makeChannel[Int](10) - val outChannel = gopherApi.makeChannel[Int](10) - r.in.connect(inChannel) - r.out.connect(outChannel) - val f0 = r.start() - import scala.concurrent.ExecutionContext.Implicits.global - var r1=0 - var r2=0 - val beforeF1 = System.currentTimeMillis - val f1 = go{ - inChannel.write(1) - inChannel.write(2) - r1 = outChannel.read - r2 = outChannel.read - } - Await.ready(f1, 10 seconds) - assert(f1.isCompleted) - assert(r.replicated.map(_.nProcessedMessages).sum == 2) - assert(r.replicated.forall(x => x.nProcessedMessages == 0 || x.nProcessedMessages == 1)) - r.stop() - } - - - test("WordCount must be replicated and accessbke via *! ports side") { - //pending - import PortAdapters._ - val nReplics = 2 - val t = gopherApi.replicate[WordCountTestTransputer](nReplics).inS.distribute{ case(id,w) => id.toInt }.control.duplicate() - val ft = t.start() - val topIn: Input[(Long,Seq[(String,Int)])] = t.topOut.*! - @volatile var nReceived = 0 - val of = gopherApi.select.forever { - case x: topIn.read @ unchecked => - //Console.println("received:"+x) - nReceived = nReceived + 1 - if (nReceived == nReplics) { - implicitly[FlowTermination[Unit]].doExit(()) - } - case o: OverflowMessage if (o==(t.overflows*!).read) => - Console.println("overflow received:"+o) - } - val outS: Output[(Long,String)] = t.inS.*! - // stack overflow in compiler. [2.11.4] - //val fw = go { - // outS.writeAll( "Some nontrivial sentence with more than one word".split(" ").toList map ((11L,_)) ) - //} - import scala.concurrent.ExecutionContext.Implicits.global - val fw = outS.awriteAll( - "Some nontrivial sentence with more than one word".split(" ").toList map ((1L,_)) - ) flatMap ( _ => - outS.awriteAll( "And in next text word 'word' will be one of top words".split(" ").toList map ((1L,_)) - ) flatMap ( _ => - outS.awriteAll( "One image is worse than thousand words".split(" ").toList map ((1L,_)) - ) ) ) flatMap { _ => - t.control.*! awrite SendTopWords(1L, 3) - } - Await.ready(fw, 10 seconds) - t.stop() - Await.ready(ft, 10 seconds) - Await.ready(of, 10 seconds) - assert(nReceived == nReplics) - } - - - def gopherApi = CommonTestObjects.gopherApi - -} - diff --git a/0.99.x/src/test/scala/gopher/transputers/TransputerRestartTest.scala b/0.99.x/src/test/scala/gopher/transputers/TransputerRestartTest.scala deleted file mode 100644 index d7c6549e..00000000 --- a/0.99.x/src/test/scala/gopher/transputers/TransputerRestartTest.scala +++ /dev/null @@ -1,153 +0,0 @@ -package gopher.transputers - -import scala.language._ -import gopher._ -import gopher.channels._ -import gopher.tags._ -import org.scalatest._ -import scala.concurrent._ -import scala.concurrent.duration._ -import akka.actor._ - -class MyException extends RuntimeException("AAA") - -trait BingoWithRecover extends SelectTransputer with TransputerLogging -{ - - val inX = InPort[Int]() - val inY = InPort[Int]() - val out = OutPort[Boolean]() - val fin = OutPort[Boolean]() - - var exReaction: SupervisorStrategy.Directive = SupervisorStrategy.Restart - var throwAlways: Boolean = false - - override def copyState(prev: Transputer):Unit = - { - val bingoPrev = prev.asInstanceOf[BingoWithRecover] - exReaction = bingoPrev.exReaction - throwAlways = bingoPrev.throwAlways - } - - - recover { - case ex: ChannelClosedException => - SupervisorStrategy.Stop - case ex: MyException => - SupervisorStrategy.Restart - } - - loop { - case x: inX.read => - val y = inY.read - out.write(x==y) - if (x==2) { - throw new MyException() - } else if (x > 2 && throwAlways) { - throw new MyException() - } - if (x==100) { - fin.write(true) - } - } - -} - - -trait Acceptor1 extends SelectTransputer -{ - - val inA = InPort[Boolean]() - - var nBingos = 0 - var nPairs = 0 - - loop { - case x: inA.read => - if (x) { - nBingos += 1 - } - nPairs += 1 - } - -} - -class TransputerRestartTest extends FunSuite -{ - - test("bingo restore with the same connectons",Now) { - val inX = gopherApi.iterableInput(1 to 100) - val inY = gopherApi.iterableInput(1 to 100) - val bingo = gopherApi.makeTransputer[BingoWithRecover] - bingo.exReaction = SupervisorStrategy.Restart - val acceptor = gopherApi.makeTransputer[Acceptor1] - val fin = gopherApi.makeChannel[Boolean]() - bingo.inX connect inX - bingo.inY connect inY - bingo.out >~~> acceptor.inA - bingo.fin connect fin - (bingo + acceptor).start() - val w = fin.aread - Await.ready(w,10 seconds) - val r = assert(acceptor.nBingos == acceptor.nPairs) - r - } - - test("bingo resume") { - val inX = gopherApi.iterableInput(1 to 100) - val inY = gopherApi.iterableInput(1 to 100) - val bingo = gopherApi.makeTransputer[BingoWithRecover] - bingo.exReaction = SupervisorStrategy.Resume - val acceptor = gopherApi.makeTransputer[Acceptor1] - val fin = gopherApi.makeChannel[Boolean]() - bingo.inX connect inX - bingo.inY connect inY - bingo.out >~~> acceptor.inA - bingo.fin connect fin - (bingo + acceptor).start() - val w = fin.aread - Await.ready(w,10 seconds) - assert(acceptor.nBingos == acceptor.nPairs) - } - - test("bingo - too many failures with restart") { - val inX = gopherApi.iterableInput(1 to 100) - val inY = gopherApi.iterableInput(1 to 100) - val bingo = gopherApi.makeTransputer[BingoWithRecover] - bingo.exReaction = SupervisorStrategy.Restart - bingo.throwAlways = true - val acceptor = gopherApi.makeTransputer[Acceptor1] - val fin = gopherApi.makeChannel[Boolean]() - bingo.inX connect inX - bingo.inY connect inY - bingo.out >~~> acceptor.inA - bingo.fin connect fin - val w = (bingo + acceptor).start() - intercept[Transputer.TooManyFailures] { - Await.result(w,10 seconds) - } - } - - test("bingo - too many failures with resume") { - val inX = gopherApi.iterableInput(1 to 100) - val inY = gopherApi.iterableInput(1 to 100) - val bingo = gopherApi.makeTransputer[BingoWithRecover] - bingo.exReaction = SupervisorStrategy.Resume - bingo.throwAlways = true - val acceptor = gopherApi.makeTransputer[Acceptor1] - val fin = gopherApi.makeChannel[Boolean]() - bingo.inX connect inX - bingo.inY connect inY - bingo.out >~~> acceptor.inA - bingo.fin connect fin - val w = (bingo + acceptor).start() - intercept[Transputer.TooManyFailures] { - Await.result(w,10 seconds) - } - } - - - def gopherApi = CommonTestObjects.gopherApi - -} - From a6927534d803d9a815a2148fbed378a4cc1c6eec Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Wed, 31 Mar 2021 08:20:16 +0300 Subject: [PATCH 065/161] added change-notes --- docs/changes-2.0.0.md | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 docs/changes-2.0.0.md diff --git a/docs/changes-2.0.0.md b/docs/changes-2.0.0.md new file mode 100644 index 00000000..a4446f1f --- /dev/null +++ b/docs/changes-2.0.0.md @@ -0,0 +1,23 @@ + +Gopher 2.0 rewritten from scratch for Scala3. + + This rewrite allows me to review some design decision [from 2013 ]: + +In gopher-0.99.x, the primary internal entity was 'flow,’ which maintains a set of channels and selectors and coordinates the call of the next asynchronous callback in a program. + +Such a scheme provides a correct algorithm for context switching but has quite limited scalability. For example, if we have few channels connected inside one flow and want to filter one of them, we can't just ignore filtered-out values. Instead, we need to propagate an empty callback to allow context switching to be transferred via our flow. + +In gopher-2.0, we change this: channels and selectors directly work with program control-flow without using intermediate entities. + + In gopher-0.99.x, channel primitives and scheduler were built on top of Akka. In gopher-2.0, we implemented primitives from scratch. + + Gopher-0.99.x provides a go statement as a wrapper around scala-async with the addition of 'go-like' error handling. +Gopher-2.0 uses monadic async/await from dotty-cps-async. Since dotty-cps-async fully supports try/catch, additional error handling constructions are now not needed. Also, translation of high-order function calls moved to dotty-cps-async. + + In gopher-0.99x select statement is represented as a match statement, which should be inside go-expression. Gopher-2.0 select statements have the form of passing a partial function to the `select` object. + +Gopher-0.99.x transputers were an exciting proof of concept, but support and maintenance were a significant part of scala-gopher support when transputers’ usage was relatively low. So, we exclude this part from the package. If you use this feature in production, you can create your port on top of scala-gopher-2.0.0 or hire me to do the same. + + +Overall, the approach from 2013 can be described as 'Rich DSL' and a special syntax for each feature, when an approach from 2021 -- minimal DSL in favor of reusing existing language constructions and minimizing the learning curve. + From 333d5f142212e168a351005f7024ec1a89ede571 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Wed, 31 Mar 2021 08:43:04 +0300 Subject: [PATCH 066/161] published 2.0.0-RC2 preview --- build.sbt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/build.sbt b/build.sbt index e55d8c6d..9b498515 100644 --- a/build.sbt +++ b/build.sbt @@ -2,9 +2,9 @@ val dottyVersion = "3.0.0-RC2" //val dottyVersion = dottyLatestNightlyBuild.get +ThisBuild/version := "2.0.0-RC2" val sharedSettings = Seq( - version := "2.0.0-RC2", organization := "com.github.rssh", scalaVersion := dottyVersion, name := "scala-gopher", @@ -21,8 +21,8 @@ lazy val root = project Sphinx / sourceDirectory := baseDirectory.value / "docs", git.remoteRepo := "git@github.com:rssh/scala-gopher.git", publishArtifact := false, - ).enablePlugins(SphinxPlugin) - .enablePlugins(GhpagesPlugin) + ).enablePlugins(GhpagesPlugin) + lazy val gopher = crossProject(JSPlatform, JVMPlatform) @@ -34,7 +34,7 @@ lazy val gopher = crossProject(JSPlatform, JVMPlatform) ).jsSettings( libraryDependencies += ("org.scala-js" %%% "scalajs-java-logging" % "1.0.0").withDottyCompat(scalaVersion.value), // TODO: switch to ModuleES ? - // scalaJSLinkerConfig ~= { _.withModuleKind(ModuleKind.CommonJSModule) }, + scalaJSLinkerConfig ~= { _.withModuleKind(ModuleKind.CommonJSModule) }, scalaJSUseMainModuleInitializer := true, ) From 0c58231ec83ee4ff942d610c5ab88d69a86c7586 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Wed, 31 Mar 2021 10:02:48 +0300 Subject: [PATCH 067/161] Update changes-2.0.0.md --- docs/changes-2.0.0.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/changes-2.0.0.md b/docs/changes-2.0.0.md index a4446f1f..df862a99 100644 --- a/docs/changes-2.0.0.md +++ b/docs/changes-2.0.0.md @@ -3,20 +3,20 @@ Gopher 2.0 rewritten from scratch for Scala3. This rewrite allows me to review some design decision [from 2013 ]: -In gopher-0.99.x, the primary internal entity was 'flow,’ which maintains a set of channels and selectors and coordinates the call of the next asynchronous callback in a program. +- In gopher-0.99.x, the primary internal entity was 'flow,’ which maintains a set of channels and selectors and coordinates the call of the next asynchronous callback in a program. Such a scheme provides a correct algorithm for context switching but has quite limited scalability. For example, if we have few channels connected inside one flow and want to filter one of them, we can't just ignore filtered-out values. Instead, we need to propagate an empty callback to allow context switching to be transferred via our flow. In gopher-2.0, we change this: channels and selectors directly work with program control-flow without using intermediate entities. - In gopher-0.99.x, channel primitives and scheduler were built on top of Akka. In gopher-2.0, we implemented primitives from scratch. +- In gopher-0.99.x, channel primitives and scheduler were built on top of Akka. In gopher-2.0, we implemented primitives from scratch. - Gopher-0.99.x provides a go statement as a wrapper around scala-async with the addition of 'go-like' error handling. +- Gopher-0.99.x provides a go statement as a wrapper around scala-async with the addition of 'go-like' error handling. Gopher-2.0 uses monadic async/await from dotty-cps-async. Since dotty-cps-async fully supports try/catch, additional error handling constructions are now not needed. Also, translation of high-order function calls moved to dotty-cps-async. - In gopher-0.99x select statement is represented as a match statement, which should be inside go-expression. Gopher-2.0 select statements have the form of passing a partial function to the `select` object. +- In gopher-0.99x select statement is represented as a match statement, which should be inside go-expression. Gopher-2.0 select statements have the form of passing a partial function to the `select` object. -Gopher-0.99.x transputers were an exciting proof of concept, but support and maintenance were a significant part of scala-gopher support when transputers’ usage was relatively low. So, we exclude this part from the package. If you use this feature in production, you can create your port on top of scala-gopher-2.0.0 or hire me to do the same. +- Gopher-0.99.x transputers were an exciting proof of concept, but support and maintenance were a significant part of scala-gopher support when transputers’ usage was relatively low. So, we exclude this part from the package. If you use this feature in production, you can create your port on top of scala-gopher-2.0.0 or hire me to do the same. Overall, the approach from 2013 can be described as 'Rich DSL' and a special syntax for each feature, when an approach from 2021 -- minimal DSL in favor of reusing existing language constructions and minimizing the learning curve. From a1b13889fb8b21164ecde68732574e2c552ef0f1 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Wed, 31 Mar 2021 10:03:16 +0300 Subject: [PATCH 068/161] Update changes-2.0.0.md --- docs/changes-2.0.0.md | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/docs/changes-2.0.0.md b/docs/changes-2.0.0.md index df862a99..3da8346c 100644 --- a/docs/changes-2.0.0.md +++ b/docs/changes-2.0.0.md @@ -3,11 +3,7 @@ Gopher 2.0 rewritten from scratch for Scala3. This rewrite allows me to review some design decision [from 2013 ]: -- In gopher-0.99.x, the primary internal entity was 'flow,’ which maintains a set of channels and selectors and coordinates the call of the next asynchronous callback in a program. - -Such a scheme provides a correct algorithm for context switching but has quite limited scalability. For example, if we have few channels connected inside one flow and want to filter one of them, we can't just ignore filtered-out values. Instead, we need to propagate an empty callback to allow context switching to be transferred via our flow. - -In gopher-2.0, we change this: channels and selectors directly work with program control-flow without using intermediate entities. +- In gopher-0.99.x, the primary internal entity was 'flow,’ which maintains a set of channels and selectors and coordinates the call of the next asynchronous callback in a program. Such a scheme provides a correct algorithm for context switching but has quite limited scalability. For example, if we have few channels connected inside one flow and want to filter one of them, we can't just ignore filtered-out values. Instead, we need to propagate an empty callback to allow context switching to be transferred via our flow. In gopher-2.0, we change this: channels and selectors directly work with program control-flow without using intermediate entities. - In gopher-0.99.x, channel primitives and scheduler were built on top of Akka. In gopher-2.0, we implemented primitives from scratch. From 2fa559684c766e7adfc720f555cd2c02661bcea1 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Wed, 31 Mar 2021 10:03:49 +0300 Subject: [PATCH 069/161] added references to changes --- README.md | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 83fa2d20..ed035910 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,8 @@ For scala2: libraryDependencies += "com.github.rssh" %% "scala-gopher" % "0.99.15" - (Documentation for scala2 look at README at 0.99x branch) +(For 0.99.x documentation look at README at 0.99x branch: https://github.com/rssh/scala-gopher/tree/0.99x) +The main differences between 0.99 and 2.0.0 is described in https://github.com/rssh/scala-gopher/blob/develop/docs/changes-2.0.0.md Scala-gopher is open source (license is Apache2); binaries are available from the maven-central repository. @@ -36,7 +37,7 @@ Note, which this is not an emulation of go language structures in Scala, but rat given Gopher[Future]() - type parameter can be a monad, which should implement CpsSchedulingMonad typeclass. + type parameter can be any monad, which should implement CpsSchedulingMonad typeclass. @@ -77,7 +78,7 @@ Channels can be buffered and unbuffered. In a unbuffered channel, write return c Also, you can use only `ReadChannel` or `WriteChannel` interfaces, where an appropriative read/write operations are defined. For `ReadChannel`, exists usual stream functions, like `map`, `zip`, `takeN`, `fold` ... etc. -For example, let we have the direct analouf of golang code: +For example, here is direct translation of golang code: ~~~ scala val channel = gopher.makeChannel[Int](100) @@ -119,8 +120,10 @@ val sum = (channel.take(1000)).fold(0)((s,i) => s+i) filtered ~~~ +(less imperative way to do the same, described later in `select.fold`). -Scala Iterable can be represented as `ReadChannel` via extension method `asReadChannel`. + +Any Iterable can be represented as `ReadChannel` via extension method `asReadChannel`. Also, we can use Scala futures as channels, which produce one value and then closes. For obtaining such input use `gopherApi.futureInput`. From 2459d5c80377b9db8ecaade5088a6cd168b065c9 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Wed, 31 Mar 2021 10:26:20 +0300 Subject: [PATCH 070/161] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ed035910..68318b60 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ For scala2: libraryDependencies += "com.github.rssh" %% "scala-gopher" % "0.99.15" (For 0.99.x documentation look at README at 0.99x branch: https://github.com/rssh/scala-gopher/tree/0.99x) -The main differences between 0.99 and 2.0.0 is described in https://github.com/rssh/scala-gopher/blob/develop/docs/changes-2.0.0.md +The main differences between 0.99 and 2.0.0 is described in https://github.com/rssh/scala-gopher/blob/master/docs/changes-2.0.0.md Scala-gopher is open source (license is Apache2); binaries are available from the maven-central repository. From 73d5da6018773926e64fc401320dbce5b6fb46da Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Fri, 2 Apr 2021 23:01:29 +0300 Subject: [PATCH 071/161] sbt-scalajs-1.5.1 --- build.sbt | 2 +- project/plugins.sbt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build.sbt b/build.sbt index 9b498515..6eb9c8f2 100644 --- a/build.sbt +++ b/build.sbt @@ -2,7 +2,7 @@ val dottyVersion = "3.0.0-RC2" //val dottyVersion = dottyLatestNightlyBuild.get -ThisBuild/version := "2.0.0-RC2" +ThisBuild/version := "2.0.0-RC3-SNAPSHOT" val sharedSettings = Seq( organization := "com.github.rssh", diff --git a/project/plugins.sbt b/project/plugins.sbt index 26bbb96a..6a6ff46e 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -3,5 +3,5 @@ addSbtPlugin("com.jsuereth" % "sbt-pgp" % "2.0.2") addSbtPlugin("com.typesafe.sbt" % "sbt-site" % "1.4.0") addSbtPlugin("com.typesafe.sbt" % "sbt-ghpages" % "0.6.3") addSbtPlugin("org.portable-scala" % "sbt-scalajs-crossproject" % "1.0.0") -addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.5.0") +addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.5.1") From abf95efcf909d28736cc294adfc9716669047989 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Tue, 20 Apr 2021 22:47:09 +0300 Subject: [PATCH 072/161] scala 2.0.0-RC3 support --- build.sbt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/build.sbt b/build.sbt index 6eb9c8f2..12dab86d 100644 --- a/build.sbt +++ b/build.sbt @@ -1,16 +1,16 @@ //val dottyVersion = "3.0.0-RC2-bin-SNAPSHOT" -val dottyVersion = "3.0.0-RC2" +val dottyVersion = "3.0.0-RC3" //val dottyVersion = dottyLatestNightlyBuild.get -ThisBuild/version := "2.0.0-RC3-SNAPSHOT" +ThisBuild/version := "2.0.0-RC3" val sharedSettings = Seq( organization := "com.github.rssh", scalaVersion := dottyVersion, name := "scala-gopher", resolvers += "Local Ivy Repository" at "file://"+Path.userHome.absolutePath+"/.ivy2/local", - libraryDependencies += "com.github.rssh" %%% "dotty-cps-async" % "0.5.0", - libraryDependencies += "org.scalameta" %%% "munit" % "0.7.23" % Test, + libraryDependencies += "com.github.rssh" %%% "dotty-cps-async" % "0.6.0", + libraryDependencies += "org.scalameta" %%% "munit" % "0.7.25" % Test, testFrameworks += new TestFramework("munit.Framework") ) From 8f3856f7a4e48ef6047f1b07b52e8aa47466dd22 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Tue, 20 Apr 2021 22:52:15 +0300 Subject: [PATCH 073/161] update deps in README --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 68318b60..1b1cc96c 100644 --- a/README.md +++ b/README.md @@ -4,9 +4,9 @@ ### Dependences: -For scala 3.0.0-RC2: +For scala 3.0.0-RC3: - libraryDependencies += "com.github.rssh" %% "scala-gopher" % "2.0.0-RC2" + libraryDependencies += "com.github.rssh" %% "scala-gopher" % "2.0.0-RC3" For scala2: From 86002943fea5f4959a4c3c1edfab197885663f5b Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Wed, 21 Apr 2021 23:21:33 +0300 Subject: [PATCH 074/161] restored reference section --- README.md | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 1b1cc96c..d34a4725 100644 --- a/README.md +++ b/README.md @@ -231,17 +231,37 @@ val multiplexed = select amap { ## Done signals. - Sometimes it is useful to receive a message when some `ReadChannel` becomes closed. Such inputs are named 'CloseableInputs' and provides a way to receive close notification in selector using `done` pseudo-type. + Sometimes it is useful to receive a message when some `ReadChannel` becomes closed. Exists way to receive close notification in selector using `done` pseudo-channel, which is available for each 'normal' channel. When channel is closed, all readers of done channels receive notifications. ~~~ scala while(!done) select{ case x:ch.read => Console.println(s"received: ${x}") - case _:ch.done => Console.println(s"done") + case _:ch.done.read => Console.println(s"done") done = true } ~~~ +# References: +---------------------- + +## Current implementation +* source code: https://github.com/rssh/scala-gopher + +## Obsolete [0.99.x] implementation: +* source code: https://github.com/rssh/scala-gopher/tree/0.99x +* presentations: + * Odessa Java/Scala Labs; Kiev Scala Meetup: Oct. 2014: http://www.slideshare.net/rssh1/scala-gopher2014 + * Wix R&D meetup. Mart 2016: http://www.slideshare.net/rssh1/csp-scala-wixmeetup2016 + * Scala Symposium. Oct. 2016. Amsterdam. http://www.slideshare.net/rssh1/scalagopher-cspstyle-programming-techniques-with-idiomatic-scala +* techreport: https://arxiv.org/abs/1611.00602 + + +## CSP-Related links: +* [Communicating Sequential Processes book by Tony Hoare](http://www.usingcsp.com) +* [brief history of CSP in Bell-labs](http://swtch.com/~rsc/thread/) +* [introduction article about go defer/recover](http://blog.golang.org/defer-panic-and-recover) + From cf65fd281666f52970ae009405bccf1e5c9f4339 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Wed, 21 Apr 2021 23:23:44 +0300 Subject: [PATCH 075/161] cosmetics --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index d34a4725..dc2dec66 100644 --- a/README.md +++ b/README.md @@ -261,7 +261,6 @@ val multiplexed = select amap { ## CSP-Related links: * [Communicating Sequential Processes book by Tony Hoare](http://www.usingcsp.com) * [brief history of CSP in Bell-labs](http://swtch.com/~rsc/thread/) -* [introduction article about go defer/recover](http://blog.golang.org/defer-panic-and-recover) From 74f7469ee17139d0c33110b22303bf00869c9ac9 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Thu, 22 Apr 2021 21:04:57 +0300 Subject: [PATCH 076/161] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index dc2dec66..a7d0d3f4 100644 --- a/README.md +++ b/README.md @@ -246,10 +246,10 @@ val multiplexed = select amap { # References: ---------------------- -## Current implementation +## 2.0.x implementation * source code: https://github.com/rssh/scala-gopher -## Obsolete [0.99.x] implementation: +## [0.99.x] implementation: * source code: https://github.com/rssh/scala-gopher/tree/0.99x * presentations: * Odessa Java/Scala Labs; Kiev Scala Meetup: Oct. 2014: http://www.slideshare.net/rssh1/scala-gopher2014 From fb2700080422ed393aa81d998cced2a5394dbdb1 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Mon, 26 Apr 2021 08:29:59 +0300 Subject: [PATCH 077/161] update to use dotty-cps-async 0.6.2 --- README.md | 2 +- build.sbt | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index dc2dec66..47319021 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ For scala 3.0.0-RC3: - libraryDependencies += "com.github.rssh" %% "scala-gopher" % "2.0.0-RC3" + libraryDependencies += "com.github.rssh" %% "scala-gopher" % "2.0.1-RC3" For scala2: diff --git a/build.sbt b/build.sbt index 12dab86d..2d55a156 100644 --- a/build.sbt +++ b/build.sbt @@ -2,14 +2,14 @@ val dottyVersion = "3.0.0-RC3" //val dottyVersion = dottyLatestNightlyBuild.get -ThisBuild/version := "2.0.0-RC3" +ThisBuild/version := "2.0.1-RC3" val sharedSettings = Seq( organization := "com.github.rssh", scalaVersion := dottyVersion, name := "scala-gopher", resolvers += "Local Ivy Repository" at "file://"+Path.userHome.absolutePath+"/.ivy2/local", - libraryDependencies += "com.github.rssh" %%% "dotty-cps-async" % "0.6.0", + libraryDependencies += "com.github.rssh" %%% "dotty-cps-async" % "0.6.2", libraryDependencies += "org.scalameta" %%% "munit" % "0.7.25" % Test, testFrameworks += new TestFramework("munit.Framework") ) From c5432ca43d6f7fb16c5df17b42630c628afb682e Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Mon, 3 May 2021 14:34:29 +0300 Subject: [PATCH 078/161] added streaming monads --- .../src/main/scala/gopher/ReadChannel.scala | 21 +++- .../impl/ChFlatMappedTryReadChannel.scala | 51 ++++++++ .../gopher/monads/ReadChannelCpsMonad.scala | 32 +++++ .../monads/ReadTryChannelCpsMonad.scala | 66 +++++++++++ .../gopher/channels/SelectSimpleSuite.scala | 44 +++++++ .../gopher/monads/ChannelMonadSuite.scala | 112 ++++++++++++++++++ .../gopher/monads/ChannelTryMonadSuite.scala | 68 +++++++++++ 7 files changed, 393 insertions(+), 1 deletion(-) create mode 100644 shared/src/main/scala/gopher/impl/ChFlatMappedTryReadChannel.scala create mode 100644 shared/src/main/scala/gopher/monads/ReadChannelCpsMonad.scala create mode 100644 shared/src/main/scala/gopher/monads/ReadTryChannelCpsMonad.scala create mode 100644 shared/src/test/scala/gopher/channels/SelectSimpleSuite.scala create mode 100644 shared/src/test/scala/gopher/monads/ChannelMonadSuite.scala create mode 100644 shared/src/test/scala/gopher/monads/ChannelTryMonadSuite.scala diff --git a/shared/src/main/scala/gopher/ReadChannel.scala b/shared/src/main/scala/gopher/ReadChannel.scala index 811c4e76..99ae588c 100644 --- a/shared/src/main/scala/gopher/ReadChannel.scala +++ b/shared/src/main/scala/gopher/ReadChannel.scala @@ -35,8 +35,15 @@ trait ReadChannel[F[_], A]: def aread():F[A] = asyncMonad.adoptCallbackStyle(f => addReader(SimpleReader(f))) + /** + * blocked read: if currently not element available - wait for one. + * Can be used only inside async block + **/ transparent inline def read(): A = await(aread())(using rAsyncMonad) + /** + * Synonim for read. + */ transparent inline def ? : A = await(aread())(using rAsyncMonad) /** @@ -57,7 +64,10 @@ trait ReadChannel[F[_], A]: case ex: ChannelClosedException => } b.result() - } + } + + transparent inline def take(n: Int): IndexedSeq[A] = + await(atake(n))(using rAsyncMonad) def aOptRead(): F[Option[A]] = asyncMonad.adoptCallbackStyle( f => @@ -191,6 +201,12 @@ end ReadChannel object ReadChannel: + + def empty[F[_],A](using Gopher[F]): ReadChannel[F,A] = + val retval = summon[Gopher[F]].makeChannel[A]() + retval.close() + retval + def fromIterable[F[_],A](c: IterableOnce[A])(using Gopher[F]): ReadChannel[F,A] = given asyncMonad: CpsSchedulingMonad[F] = summon[Gopher[F]].asyncMonad val retval = makeChannel[A]() @@ -205,12 +221,15 @@ object ReadChannel: retval + def fromFuture[F[_],A](f: F[A])(using Gopher[F]): ReadChannel[F,A] = futureInput(f) def fromValues[F[_],A](values: A*)(using Gopher[F]): ReadChannel[F,A] = fromIterable(values) + + end ReadChannel diff --git a/shared/src/main/scala/gopher/impl/ChFlatMappedTryReadChannel.scala b/shared/src/main/scala/gopher/impl/ChFlatMappedTryReadChannel.scala new file mode 100644 index 00000000..cbec571b --- /dev/null +++ b/shared/src/main/scala/gopher/impl/ChFlatMappedTryReadChannel.scala @@ -0,0 +1,51 @@ +package gopher.impl + +import cps._ +import gopher._ +import scala.util._ +import scala.util.control._ + +class ChFlatMappedTryReadChannel[F[_], A, B](prev: ReadChannel[F,Try[A]], f: Try[A]=>ReadChannel[F,Try[B]]) extends ReadChannel[F,Try[B]] { + + def addReader(reader: Reader[Try[B]]): Unit = + bChannel.addReader(reader) + + + def addDoneReader(reader: Reader[Unit]): Unit = { + bChannel.addDoneReader(reader) + } + + def gopherApi:Gopher[F] = prev.gopherApi + + val bChannel = gopherApi.makeChannel[Try[B]]() + + def run(): F[Unit] = { + given CpsSchedulingMonad[F] = gopherApi.asyncMonad + async[F]{ + while{ + prev.optRead() match + case None => false + case Some(v) => + val internal: ReadChannel[F,Try[B]] = + try + f(v) + catch + case NonFatal(ex) => + ReadChannel.fromValues[F,Try[B]](Failure(ex))(using gopherApi) + while{ + internal.optRead() match + case None => false + case Some(v) => + bChannel.write(v) + true + } do () + true + } do () + bChannel.close() + } + } + + gopherApi.asyncMonad.spawn(run()) + + +} diff --git a/shared/src/main/scala/gopher/monads/ReadChannelCpsMonad.scala b/shared/src/main/scala/gopher/monads/ReadChannelCpsMonad.scala new file mode 100644 index 00000000..15a89eae --- /dev/null +++ b/shared/src/main/scala/gopher/monads/ReadChannelCpsMonad.scala @@ -0,0 +1,32 @@ +package gopher.monads + +import gopher._ +import cps._ + +import gopher.impl._ + + + + +given ReadChannelCpsMonad[F[_]](using Gopher[F]): CpsMonad[ [A] =>> ReadChannel[F,A]] with + + def pure[T](t:T): ReadChannel[F,T] = + ReadChannel.fromValues[F,T](t) + + def map[A,B](fa: ReadChannel[F,A])(f: A=>B): ReadChannel[F,B] = + fa.map(f) + + def flatMap[A,B](fa: ReadChannel[F,A])(f: A=>ReadChannel[F,B]): ReadChannel[F,B] = + new ChFlatMappedReadChannel[F,A,B](fa,f) + + +given futureToReadChannel[F[_]](using Gopher[F]): CpsMonadConversion[F, [A]=>>ReadChannel[F,A]] with + + def apply[T](m: CpsMonad[F], mg: CpsMonad[[A] =>> ReadChannel[F,A]], ft: F[T]): ReadChannel[F,T] = + futureInput(ft) + + + + + + diff --git a/shared/src/main/scala/gopher/monads/ReadTryChannelCpsMonad.scala b/shared/src/main/scala/gopher/monads/ReadTryChannelCpsMonad.scala new file mode 100644 index 00000000..fd2e56ee --- /dev/null +++ b/shared/src/main/scala/gopher/monads/ReadTryChannelCpsMonad.scala @@ -0,0 +1,66 @@ +package gopher.monads + +import scala.util._ +import gopher._ +import cps._ + +import gopher.impl._ + + +given ReadTryChannelCpsMonad[F[_]](using Gopher[F]): CpsAsyncMonad[ [A] =>> ReadChannel[F,Try[A]] ] with + + type FW[T] = [A] =>> ReadChannel[F,Try[A]] + + def pure[T](t:T): ReadChannel[F,Try[T]] = + ReadChannel.fromValues[F,Try[T]](Success(t)) + + def map[A,B](fa: ReadChannel[F,Try[A]])(f: A=>B): ReadChannel[F,Try[B]] = + fa.map{ + case Success(a) => + try{ + Success(f(a)) + } catch { + case ex: Throwable => Failure(ex) + } + case Failure(ex) => Failure(ex) + } + + def flatMap[A,B](fa: ReadChannel[F,Try[A]])(f: A=>ReadChannel[F,Try[B]]): ReadChannel[F,Try[B]] = + new ChFlatMappedTryReadChannel(fa,{ + case Success(a) => f(a) + case Failure(ex) => ReadChannel.fromValues[F,Try[B]](Failure(ex )) + }) + + def flatMapTry[A,B](fa: ReadChannel[F,Try[A]])(f: Try[A] => ReadChannel[F,Try[B]]): ReadChannel[F,Try[B]] = + new ChFlatMappedTryReadChannel(fa,f) + + def error[A](e: Throwable): ReadChannel[F,Try[A]] = + val r = makeChannel[Try[A]]() + given fm: CpsSchedulingMonad[F] = summon[Gopher[F]].asyncMonad + fm.spawn{ async[F] { + r.write(Failure(e)) + r.close() + } } + r + + + def adoptCallbackStyle[A](source: (Try[A]=>Unit) => Unit): ReadChannel[F,Try[A]] = { + val r = makeOnceChannel[Try[A]]() + given fm: CpsSchedulingMonad[F] = summon[Gopher[F]].asyncMonad + val fv = fm.adoptCallbackStyle(source) + fm.spawn{ + fm.flatMapTry( fv ){ tryV => + r.awrite(tryV) + } + } + r + } + + +given readChannelToTryReadChannel[F[_]](using Gopher[F]): CpsMonadConversion[ [A]=>>ReadChannel[F,A], [A]=>>ReadChannel[F,Try[A]]] with + + def apply[T](m: CpsMonad[[A]=>>ReadChannel[F,A]], mg: CpsMonad[[A] =>> ReadChannel[F,Try[A]]], ft: ReadChannel[F,T]): ReadChannel[F,Try[T]] = + ft.map(x => Success(x)) + + + diff --git a/shared/src/test/scala/gopher/channels/SelectSimpleSuite.scala b/shared/src/test/scala/gopher/channels/SelectSimpleSuite.scala new file mode 100644 index 00000000..17e08703 --- /dev/null +++ b/shared/src/test/scala/gopher/channels/SelectSimpleSuite.scala @@ -0,0 +1,44 @@ +package gopher.channels + + +import munit._ +import scala.language.postfixOps +import scala.concurrent._ +import scala.concurrent.duration._ + +import cps._ +import gopher._ +import cps.monads.FutureAsyncMonad + +class SelectSimpleSuite extends FunSuite +{ + + import scala.concurrent.ExecutionContext.Implicits.global + given Gopher[Future] = SharedGopherAPI.apply[Future]() + + + test("simple select in a loop") { + val ch = makeChannel[Int]() + var sum = 0 + val loop = async { + var done = false + while(!done) { + select { + case x: ch.read => + sum = sum+x + if (x > 100) { + done = true + } + } + } + } + ch.awriteAll(1 to 200) + + async { + await(loop) + assert( sum == (1 to 101).sum) + } + } + + +} diff --git a/shared/src/test/scala/gopher/monads/ChannelMonadSuite.scala b/shared/src/test/scala/gopher/monads/ChannelMonadSuite.scala new file mode 100644 index 00000000..8760ce74 --- /dev/null +++ b/shared/src/test/scala/gopher/monads/ChannelMonadSuite.scala @@ -0,0 +1,112 @@ +package gopher.monadexample + +import cps.* +import gopher.* +import munit.* + +import scala.concurrent.* +import scala.concurrent.duration.* +import scala.collection.SortedSet + +import cps.monads.FutureAsyncMonad +import gopher.monads.given + +class ChannelMonadSuite extends FunSuite { + + import scala.concurrent.ExecutionContext.Implicits.global + given Gopher[Future] = SharedGopherAPI.apply[Future]() + + + test("using channel as monad and read inside") { + + val chX = ReadChannel.fromValues(1,2,3,4,5) + val chY = ReadChannel.fromValues(1,2,3,4,5) + + val squares = async[[X] =>> ReadChannel[Future,X]] { + val x = await(chX) + //println(s"reading from X $x") + val y = chY.read() + //println(s"reading from Y $y") + x*y + } + + async[Future] { + val a1 = squares.read() + //println(s"a1==${a1}") + assert(a1 == 1) + val a2 = squares.read() + //println(s"a2==${a2}") + assert(a2 == 4) + val a3 = squares.read() + assert(a3 == 9) + val a4 = squares.read() + assert(a4 == 16) + val a5 = squares.read() + assert(a5 == 25) + } + + } + + test("using channel with flatMap") { + + val chX = ReadChannel.fromValues(1,2,3,4,5) + + val r = async[[X] =>> ReadChannel[Future,X]] { + val x = await(chX) + val fy = if (x %2 == 0) ReadChannel.empty[Future,Int] else ReadChannel.fromIterable(1 to x) + await(fy)*x + } + + async[Future] { + val seq = r.take(20) + //println(seq) + assert(seq(0)==1) + assert(seq(1)==3) + assert(seq(2)==6) + assert(seq(3)==9) + assert(seq(4)==5) + assert(seq(5)==10) + } + + + } + + + test("sieve inside channel monad") { + + val n = 100 + val r = async[[X] =>> ReadChannel[Future,X]] { + var initial = ReadChannel.fromIterable[Future,Int](2 to n) + val drop = ReadChannel.empty[Future,Int] + var prevs = IndexedSeq.empty[Int] + var done = false + val x = await(initial) + if !prevs.isEmpty then + var pi = 0 + while{ + var p = prevs(pi) + if (x % p == 0) then + val r = await(drop) + pi = pi+1 + p*p < x + } do () + prevs = prevs :+ x + x + } + + async[Future] { + val primes = r.take(20) + //println(s"primes: $primes") + assert(primes(0)==2) + assert(primes(1)==3) + assert(primes(2)==5) + assert(primes(3)==7) + assert(primes(6)==17) + assert(primes(9)==29) + } + + + } + + +} diff --git a/shared/src/test/scala/gopher/monads/ChannelTryMonadSuite.scala b/shared/src/test/scala/gopher/monads/ChannelTryMonadSuite.scala new file mode 100644 index 00000000..421049b1 --- /dev/null +++ b/shared/src/test/scala/gopher/monads/ChannelTryMonadSuite.scala @@ -0,0 +1,68 @@ +package gopher.monadexample + +import cps.* +import gopher.* +import munit.* + +import scala.concurrent.* +import scala.concurrent.duration.* +import scala.util.* + +import cps.monads.FutureAsyncMonad +import gopher.monads.given + +class ChannelTryMonadSuite extends FunSuite { + + import scala.concurrent.ExecutionContext.Implicits.global + given Gopher[Future] = SharedGopherAPI.apply[Future]() + + + test("failure inside async with ReadTryChannelCpsMonad") { + + val r = async[[X]=>>ReadChannel[Future,Try[X]]] { + val ch1 = ReadChannel.fromIterable[Future,Int](1 to 10) + val x = await(ch1) + if (x % 3 == 0) then + throw new RuntimeException("AAA") + x + } + + async[Future] { + val a1 = r.read() + val a2 = r.read() + val a3 = r.read() + assert(a1 == Success(1)) + assert(a2 == Success(2)) + assert(a3.isFailure) + } + + } + + test("using try/catch along with ReadTryChannelCpsMonad") { + + val r = async[[X]=>>ReadChannel[Future,Try[X]]] { + val ch1 = ReadChannel.fromIterable[Future,Int](1 to 10) + val x = await(ch1) + try { + if (x % 3 == 0) { + throw (new RuntimeException("AAA")) + } + x + } catch { + case ex: RuntimeException => + 100 + } + } + + async[Future] { + val a1 = r.read() + val a2 = r.read() + val a3 = r.read() + assert(a1 == Success(1)) + assert(a2 == Success(2)) + assert(a3 == Success(100)) + } + + } + +} \ No newline at end of file From 632445b12f827329c49fddc37bd2ff65829fdaa1 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Sat, 8 May 2021 14:07:39 +0300 Subject: [PATCH 079/161] Fiexed order of read-done and read in DuplicatedChannel, to make done always be called first. --- jvm/src/main/scala/gopher/JVMGopher.scala | 2 + .../gopher/impl/GuardedSPSCBaseChannel.scala | 10 ++- .../impl/GuardedSPSCBufferedChannel.scala | 1 + .../DuppedChannelsMultipleSuite.scala | 87 +++++++++++++++++++ .../src/main/scala/gopher/ReadChannel.scala | 3 + .../src/main/scala/gopher/SelectGroup.scala | 2 + shared/src/main/scala/gopher/SelectLoop.scala | 7 +- .../main/scala/gopher/impl/DuppedInput.scala | 22 ++--- 8 files changed, 118 insertions(+), 16 deletions(-) create mode 100644 jvm/src/test/scala/gopher/channels/DuppedChannelsMultipleSuite.scala diff --git a/jvm/src/main/scala/gopher/JVMGopher.scala b/jvm/src/main/scala/gopher/JVMGopher.scala index 77763367..e6b0c7a0 100644 --- a/jvm/src/main/scala/gopher/JVMGopher.scala +++ b/jvm/src/main/scala/gopher/JVMGopher.scala @@ -72,4 +72,6 @@ object JVMGopher extends GopherAPI: } + final val MAX_SPINS = 400 + val Gopher = JVMGopher diff --git a/jvm/src/main/scala/gopher/impl/GuardedSPSCBaseChannel.scala b/jvm/src/main/scala/gopher/impl/GuardedSPSCBaseChannel.scala index 9f05114d..9265d6b8 100644 --- a/jvm/src/main/scala/gopher/impl/GuardedSPSCBaseChannel.scala +++ b/jvm/src/main/scala/gopher/impl/GuardedSPSCBaseChannel.scala @@ -12,6 +12,8 @@ import scala.util.Try import scala.util.Success import scala.util.Failure +import java.util.logging.{Level => LogLevel} + /** * Guarded channel work in the next way: @@ -33,7 +35,6 @@ abstract class GuardedSPSCBaseChannel[F[_]:CpsAsyncMonad,A](override val gopherA protected val stepGuard = new AtomicInteger(STEP_FREE) protected val stepRunnable: Runnable = (()=>entryStep()) - def addReader(reader: Reader[A]): Unit = if (reader.canExpire) then @@ -66,6 +67,7 @@ abstract class GuardedSPSCBaseChannel[F[_]:CpsAsyncMonad,A](override val gopherA protected def entryStep(): Unit = var done = false + var nSpins = 0 while(!done) { if (stepGuard.compareAndSet(STEP_FREE,STEP_BUSY)) { done = true @@ -77,6 +79,7 @@ abstract class GuardedSPSCBaseChannel[F[_]:CpsAsyncMonad,A](override val gopherA done = true } else { // other set updates, we should spinLock + nSpins = nSpins + 1 Thread.onSpinWait() } } @@ -99,7 +102,7 @@ abstract class GuardedSPSCBaseChannel[F[_]:CpsAsyncMonad,A](override val gopherA while(!readers.isEmpty) { val r = readers.poll() if (!(r eq null) && !r.isExpired) then - r.capture() match + r.capture() match case Some(f) => progress = true taskExecutor.execute(() => f(Failure(new ChannelClosedException())) ) @@ -173,9 +176,12 @@ abstract class GuardedSPSCBaseChannel[F[_]:CpsAsyncMonad,A](override val gopherA if (!v.isExpired) if (queue.isEmpty) Thread.onSpinWait() + // if (nSpins > JVMGopher.MAX_SPINS) + // Thread.`yield`() queue.addLast(v) + object GuardedSPSCBaseChannel: final val STEP_FREE = 0 diff --git a/jvm/src/main/scala/gopher/impl/GuardedSPSCBufferedChannel.scala b/jvm/src/main/scala/gopher/impl/GuardedSPSCBufferedChannel.scala index 69b39879..641de118 100644 --- a/jvm/src/main/scala/gopher/impl/GuardedSPSCBufferedChannel.scala +++ b/jvm/src/main/scala/gopher/impl/GuardedSPSCBufferedChannel.scala @@ -9,6 +9,7 @@ import scala.util.Try import scala.util.Success import scala.util.Failure +import java.util.logging.{Level => LogLevel} class GuardedSPSCBufferedChannel[F[_]:CpsAsyncMonad,A](gopherApi: JVMGopher[F], bufSize: Int, controlExecutor: ExecutorService, diff --git a/jvm/src/test/scala/gopher/channels/DuppedChannelsMultipleSuite.scala b/jvm/src/test/scala/gopher/channels/DuppedChannelsMultipleSuite.scala new file mode 100644 index 00000000..b598437f --- /dev/null +++ b/jvm/src/test/scala/gopher/channels/DuppedChannelsMultipleSuite.scala @@ -0,0 +1,87 @@ +package gopher.channels + +import cps._ +import cps.monads.FutureAsyncMonad +import gopher._ +import munit._ + +import scala.concurrent._ +import scala.concurrent.duration._ +import scala.language.postfixOps +import scala.util._ + +class DuppedChannelsMultipleSuite extends FunSuite { + + import scala.concurrent.ExecutionContext.Implicits.global + given gopherApi: Gopher[Future] = SharedGopherAPI.apply[Future]() + + val inMemoryLog = new java.util.concurrent.ConcurrentLinkedQueue[(Int, Long, String, Throwable)]() + + + test("on closing of main stream dupped outputs also closed N times.") { + val N = 1000 + var logIndex = 0 + gopherApi.setLogFun( (level,msg, ex) => inMemoryLog.add((logIndex, Thread.currentThread().getId(), msg,ex)) ) + for(i <- 1 to N) { + logIndex = i + val ch = makeChannel[Int](1) + val (in1, in2) = ch.dup() + val f1 = async{ + ch.write(1) + ch.close() + } + val f = for{ fx <- f1 + x <- in1.aread() + r <- in1.aread().transformWith { + case Success(u) => + Future failed new IllegalStateException("Mist be closed") + case Failure(u) => + Future successful (assert(x == 1)) + } + } yield { + r + } + try { + val r = Await.result(f, 30 seconds); + }catch{ + case ex: TimeoutException => + showTraces(20) + println("---") + showInMemoryLog() + throw ex + } + } + + } + + def showTraces(maxTracesToShow: Int): Unit = { + val traces = Thread.getAllStackTraces(); + val it = traces.entrySet().iterator() + while(it.hasNext()) { + val e = it.next(); + println(e.getKey()); + val elements = e.getValue() + var sti = 0 + var wasPark = false + while(sti < elements.length && sti < maxTracesToShow && !wasPark) { + val st = elements(sti) + println(" "*10 + st) + sti = sti + 1; + wasPark = (st.getMethodName == "park") + } + } + } + + def showInMemoryLog(): Unit = { + while(!inMemoryLog.isEmpty) { + val r = inMemoryLog.poll() + if (r != null) { + println(r) + } + } + } + + +} + + diff --git a/shared/src/main/scala/gopher/ReadChannel.scala b/shared/src/main/scala/gopher/ReadChannel.scala index 99ae588c..4852a3b2 100644 --- a/shared/src/main/scala/gopher/ReadChannel.scala +++ b/shared/src/main/scala/gopher/ReadChannel.scala @@ -7,6 +7,9 @@ import scala.util.Success import scala.util.Failure import scala.concurrent.duration.Duration +import java.util.logging.{Level => LogLevel} + + trait ReadChannel[F[_], A]: thisReadChannel => diff --git a/shared/src/main/scala/gopher/SelectGroup.scala b/shared/src/main/scala/gopher/SelectGroup.scala index 3ed8c797..e88e28c3 100644 --- a/shared/src/main/scala/gopher/SelectGroup.scala +++ b/shared/src/main/scala/gopher/SelectGroup.scala @@ -10,6 +10,8 @@ import scala.util._ import scala.concurrent.duration._ import scala.language.postfixOps +import java.util.logging.{Level => LogLevel} + /** * Select group is a virtual 'lock' object, where only diff --git a/shared/src/main/scala/gopher/SelectLoop.scala b/shared/src/main/scala/gopher/SelectLoop.scala index c60d251f..d97b5207 100644 --- a/shared/src/main/scala/gopher/SelectLoop.scala +++ b/shared/src/main/scala/gopher/SelectLoop.scala @@ -5,6 +5,9 @@ import scala.quoted._ import scala.compiletime._ import scala.concurrent.duration._ +import java.util.logging.{Level => LogLevel} + + class SelectLoop[F[_]](api: Gopher[F]) extends SelectGroupBuilder[F,Boolean, Unit](api): @@ -25,7 +28,3 @@ class SelectLoop[F[_]](api: Gopher[F]) extends SelectGroupBuilder[F,Boolean, Uni } - - - - diff --git a/shared/src/main/scala/gopher/impl/DuppedInput.scala b/shared/src/main/scala/gopher/impl/DuppedInput.scala index c69aaac0..1d4b6e5b 100644 --- a/shared/src/main/scala/gopher/impl/DuppedInput.scala +++ b/shared/src/main/scala/gopher/impl/DuppedInput.scala @@ -8,6 +8,7 @@ import scala.util._ import java.util.concurrent.ConcurrentLinkedQueue import java.util.concurrent.atomic.AtomicInteger +import java.util.logging.{Level => LogLevel} @@ -21,15 +22,16 @@ class DuppedInput[F[_],A](origin:ReadChannel[F,A], bufSize: Int=1)(using api:Gop given CpsSchedulingMonad[F] = api.asyncMonad - val runner = SelectLoop[F](api).onReadAsync(origin){a => async{ - val f1 = sink1.write(a) - val f2 = sink2.write(a) - true - }}.onRead(origin.done){ _ => - sink1.close() - sink2.close() - false - }.runAsync() - api.asyncMonad.spawn(runner) + val runner = SelectLoop[F](api). + onRead(origin.done){ _ => + sink1.close() + sink2.close() + false + }.onReadAsync(origin){a => async{ + val f1 = sink1.write(a) + val f2 = sink2.write(a) + true + }}.runAsync() + api.asyncMonad.spawn(runner) } From e8e2e7a7c10369e09d32db05fbc3d14dfb3d844a Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Sat, 8 May 2021 15:22:38 +0300 Subject: [PATCH 080/161] order caseDefs in select, to make doneCases be first during building reading callback expressions. This is needed for case, when done event was fired and we attach build expression to it. --- shared/src/main/scala/gopher/Select.scala | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/shared/src/main/scala/gopher/Select.scala b/shared/src/main/scala/gopher/Select.scala index 905deb1f..c84a1a4e 100644 --- a/shared/src/main/scala/gopher/Select.scala +++ b/shared/src/main/scala/gopher/Select.scala @@ -106,7 +106,7 @@ object Select: type Monad[X] = F[X] def appended[L <: SelectListeners[F,S,R] : Type](base: Expr[L])(using Quotes): Expr[L] - case class ReadExpression[F[_]:Type, A:Type, S:Type, R:Type](ch: Expr[ReadChannel[F,A]], f: Expr[A => S]) extends SelectorCaseExpr[F,S,R]: + case class ReadExpression[F[_]:Type, A:Type, S:Type, R:Type](ch: Expr[ReadChannel[F,A]], f: Expr[A => S], isDone: Boolean) extends SelectorCaseExpr[F,S,R]: def appended[L <: SelectListeners[F,S,R]: Type](base: Expr[L])(using Quotes): Expr[L] = '{ $base.onRead($ch)($f) } @@ -183,7 +183,16 @@ object Select: //if (caseExprs.find(_.isInstanceOf[DefaultExpression[?]]).isDefined) { // report.error("default is not supported") //} - builder(cases.map(parseCaseDef[F,A,B](_))) + val unorderedCases = cases.map(parseCaseDef[F,A,B](_)) + // done should be + val (isDone,notDone) = unorderedCases.partition{ x => + x match + case DoneExression(_,_) => true + case ReadExpression(_,_,isDone) => isDone + case _ => false + } + val doneFirstCases = isDone ++ notDone + builder(doneFirstCases) def parseCaseDef[F[_]:Type,S:Type,R:Type](using Quotes)(caseDef: quotes.reflect.CaseDef): SelectorCaseExpr[F,S,R] = @@ -195,7 +204,11 @@ object Select: val readFun = makeLambda(valName,tp,bind.symbol,caseDef.rhs) if (channel.tpe <:< TypeRepr.of[ReadChannel[F,?]]) tp.asType match - case '[a] => ReadExpression(channel.asExprOf[ReadChannel[F,a]],readFun.asExprOf[a=>S]) + case '[a] => + val isDone = channel match + case quotes.reflect.Select(ch1,"done") if (ch1.tpe <:< TypeRepr.of[ReadChannel[F,?]]) => true + case _ => false + ReadExpression(channel.asExprOf[ReadChannel[F,a]],readFun.asExprOf[a=>S],isDone) case _ => reportError("can't determinate read type", caseDef.pattern.asExpr) else From bb47755334fa9ee2bfe91693090abc5691bea20e Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Sat, 8 May 2021 16:09:13 +0300 Subject: [PATCH 081/161] log failures which can be in spawned operations --- shared/src/main/scala/gopher/Gopher.scala | 12 ++++++++++-- shared/src/main/scala/gopher/ReadChannel.scala | 4 ++-- shared/src/main/scala/gopher/Select.scala | 2 +- shared/src/main/scala/gopher/SelectGroup.scala | 6 +++--- .../scala/gopher/impl/ChFlatMappedReadChannel.scala | 2 +- .../gopher/impl/ChFlatMappedTryReadChannel.scala | 2 +- shared/src/main/scala/gopher/impl/DuppedInput.scala | 2 +- .../main/scala/gopher/impl/FilteredReadChannel.scala | 2 +- .../main/scala/gopher/impl/MappedReadChannel.scala | 2 +- .../scala/gopher/monads/ReadTryChannelCpsMonad.scala | 4 ++-- 10 files changed, 23 insertions(+), 15 deletions(-) diff --git a/shared/src/main/scala/gopher/Gopher.scala b/shared/src/main/scala/gopher/Gopher.scala index 8276c413..b30034e1 100644 --- a/shared/src/main/scala/gopher/Gopher.scala +++ b/shared/src/main/scala/gopher/Gopher.scala @@ -2,7 +2,7 @@ package gopher import cps._ import scala.concurrent.duration.Duration -import scala.util.Try +import scala.util._ import java.util.logging.{Level => LogLevel} @@ -33,6 +33,14 @@ trait Gopher[F[_]:CpsSchedulingMonad]: protected[gopher] def logImpossible(ex: Throwable): Unit = log(LogLevel.WARNING, "impossible", ex) + protected[gopher] def spawnAndLogFail[T](op: =>F[T]): F[Unit] = + asyncMonad.mapTry(asyncMonad.spawn(op)){ + case Success(_) => () + case Failure(ex) => + log(LogLevel.WARNING, "exception in spawned process", ex) + () + } + def makeChannel[A](bufSize:Int = 0, autoClose: Boolean = false)(using g:Gopher[?]):Channel[g.Monad,A,A] = @@ -46,7 +54,7 @@ def select(using g:Gopher[?]):Select[g.Monad] = def futureInput[F[_],A](f: F[A])(using g: Gopher[F]): ReadChannel[F,A] = val ch = g.makeOnceChannel[Try[A]]() - g.asyncMonad.spawn{ + g.spawnAndLogFail{ g.asyncMonad.flatMapTry(f)(r => ch.awrite(r)) } ch.map(_.get) diff --git a/shared/src/main/scala/gopher/ReadChannel.scala b/shared/src/main/scala/gopher/ReadChannel.scala index 4852a3b2..d3dc8a86 100644 --- a/shared/src/main/scala/gopher/ReadChannel.scala +++ b/shared/src/main/scala/gopher/ReadChannel.scala @@ -149,7 +149,7 @@ trait ReadChannel[F[_], A]: def zip[B](x: ReadChannel[F,B]): ReadChannel[F,(A,B)] = given CpsSchedulingMonad[F] = asyncMonad val retval = gopherApi.makeChannel[(A,B)]() - asyncMonad.spawn(async[F]{ + gopherApi.spawnAndLogFail(async[F]{ var done = false while(!done) { this.optRead() match @@ -213,7 +213,7 @@ object ReadChannel: def fromIterable[F[_],A](c: IterableOnce[A])(using Gopher[F]): ReadChannel[F,A] = given asyncMonad: CpsSchedulingMonad[F] = summon[Gopher[F]].asyncMonad val retval = makeChannel[A]() - asyncMonad.spawn(async{ + summon[Gopher[F]].spawnAndLogFail(async{ val it = c.iterator while(it.hasNext) { val a = it.next() diff --git a/shared/src/main/scala/gopher/Select.scala b/shared/src/main/scala/gopher/Select.scala index c84a1a4e..6bf9f88f 100644 --- a/shared/src/main/scala/gopher/Select.scala +++ b/shared/src/main/scala/gopher/Select.scala @@ -60,7 +60,7 @@ class Select[F[_]](api: Gopher[F]): def mapAsync[A](step: SelectGroup[F,A] => F[A]): ReadChannel[F,A] = val r = makeChannel[A]()(using api) given CpsSchedulingMonad[F] = api.asyncMonad - api.asyncMonad.spawn{ + api.spawnAndLogFail{ async{ var done = false while(!done) diff --git a/shared/src/main/scala/gopher/SelectGroup.scala b/shared/src/main/scala/gopher/SelectGroup.scala index e88e28c3..3d62d7e0 100644 --- a/shared/src/main/scala/gopher/SelectGroup.scala +++ b/shared/src/main/scala/gopher/SelectGroup.scala @@ -169,7 +169,7 @@ class SelectGroup[F[_], S](api: Gopher[F]) extends SelectListeners[F,S,S]: if waitState.compareAndSet(0,1) then Some(v => { timeoutScheduled.foreach(_.cancel()) - m.spawn( + api.spawnAndLogFail( m.mapTry(action(v))(x => call(x)) ) }) @@ -189,7 +189,7 @@ class SelectGroup[F[_], S](api: Gopher[F]) extends SelectListeners[F,S,S]: if waitState.compareAndSet(0,1) then Some((element, (v:Try[Unit]) => { timeoutScheduled.foreach(_.cancel()) - m.spawn( + api.spawnAndLogFail( m.mapTry(action(v))(x=>call(x)) )} )) @@ -204,7 +204,7 @@ class SelectGroup[F[_], S](api: Gopher[F]) extends SelectListeners[F,S,S]: def capture(): Option[Try[FiniteDuration] => Unit] = if (waitState.compareAndSet(0,1)) then Some((v:Try[FiniteDuration]) => - m.spawn(m.mapTry(action(v))(x => call(x))) + api.spawnAndLogFail(m.mapTry(action(v))(x => call(x))) ) else None diff --git a/shared/src/main/scala/gopher/impl/ChFlatMappedReadChannel.scala b/shared/src/main/scala/gopher/impl/ChFlatMappedReadChannel.scala index d14355b3..ba7fe7a4 100644 --- a/shared/src/main/scala/gopher/impl/ChFlatMappedReadChannel.scala +++ b/shared/src/main/scala/gopher/impl/ChFlatMappedReadChannel.scala @@ -40,7 +40,7 @@ class ChFlatMappedReadChannel[F[_], A, B](prev: ReadChannel[F,A], f: A=>ReadChan bChannel.close() } - gopherApi.asyncMonad.spawn(run()) + gopherApi.spawnAndLogFail(run()) diff --git a/shared/src/main/scala/gopher/impl/ChFlatMappedTryReadChannel.scala b/shared/src/main/scala/gopher/impl/ChFlatMappedTryReadChannel.scala index cbec571b..d39f56ed 100644 --- a/shared/src/main/scala/gopher/impl/ChFlatMappedTryReadChannel.scala +++ b/shared/src/main/scala/gopher/impl/ChFlatMappedTryReadChannel.scala @@ -45,7 +45,7 @@ class ChFlatMappedTryReadChannel[F[_], A, B](prev: ReadChannel[F,Try[A]], f: Try } } - gopherApi.asyncMonad.spawn(run()) + gopherApi.spawnAndLogFail(run()) } diff --git a/shared/src/main/scala/gopher/impl/DuppedInput.scala b/shared/src/main/scala/gopher/impl/DuppedInput.scala index 1d4b6e5b..2485c76b 100644 --- a/shared/src/main/scala/gopher/impl/DuppedInput.scala +++ b/shared/src/main/scala/gopher/impl/DuppedInput.scala @@ -32,6 +32,6 @@ class DuppedInput[F[_],A](origin:ReadChannel[F,A], bufSize: Int=1)(using api:Gop val f2 = sink2.write(a) true }}.runAsync() - api.asyncMonad.spawn(runner) + api.spawnAndLogFail(runner) } diff --git a/shared/src/main/scala/gopher/impl/FilteredReadChannel.scala b/shared/src/main/scala/gopher/impl/FilteredReadChannel.scala index 868a3d2e..41eeca95 100644 --- a/shared/src/main/scala/gopher/impl/FilteredReadChannel.scala +++ b/shared/src/main/scala/gopher/impl/FilteredReadChannel.scala @@ -62,7 +62,7 @@ class FilteredAsyncReadChannel[F[_],A](internal: ReadChannel[F,A], p: A=>F[Boole def wrappedFun(fun: (Try[A] => Unit) ): (Try[A] => Unit) = { case Success(a) => - gopherApi.asyncMonad.spawn( + gopherApi.spawnAndLogFail( gopherApi.asyncMonad.mapTry(p(a)){ case Success(v) => if (v) { diff --git a/shared/src/main/scala/gopher/impl/MappedReadChannel.scala b/shared/src/main/scala/gopher/impl/MappedReadChannel.scala index 27ee86f9..de8a0fa9 100644 --- a/shared/src/main/scala/gopher/impl/MappedReadChannel.scala +++ b/shared/src/main/scala/gopher/impl/MappedReadChannel.scala @@ -51,7 +51,7 @@ class MappedAsyncReadChannel[F[_],A, B](internal: ReadChannel[F,A], f: A=> F[B]) def wrappedFun(fun: (Try[B] => Unit) ): (Try[A] => Unit) = { case Success(a) => try{ - asyncMonad.spawn( + gopherApi.spawnAndLogFail( asyncMonad.mapTry(f(a))(fun) ) }catch{ diff --git a/shared/src/main/scala/gopher/monads/ReadTryChannelCpsMonad.scala b/shared/src/main/scala/gopher/monads/ReadTryChannelCpsMonad.scala index fd2e56ee..39350134 100644 --- a/shared/src/main/scala/gopher/monads/ReadTryChannelCpsMonad.scala +++ b/shared/src/main/scala/gopher/monads/ReadTryChannelCpsMonad.scala @@ -37,7 +37,7 @@ given ReadTryChannelCpsMonad[F[_]](using Gopher[F]): CpsAsyncMonad[ [A] =>> Read def error[A](e: Throwable): ReadChannel[F,Try[A]] = val r = makeChannel[Try[A]]() given fm: CpsSchedulingMonad[F] = summon[Gopher[F]].asyncMonad - fm.spawn{ async[F] { + summon[Gopher[F]].spawnAndLogFail{ async[F] { r.write(Failure(e)) r.close() } } @@ -48,7 +48,7 @@ given ReadTryChannelCpsMonad[F[_]](using Gopher[F]): CpsAsyncMonad[ [A] =>> Read val r = makeOnceChannel[Try[A]]() given fm: CpsSchedulingMonad[F] = summon[Gopher[F]].asyncMonad val fv = fm.adoptCallbackStyle(source) - fm.spawn{ + summon[Gopher[F]].spawnAndLogFail{ fm.flatMapTry( fv ){ tryV => r.awrite(tryV) } From 8eacc84068bcc3d5e4f893fb2ee7f8a89c2e24da Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Sun, 9 May 2021 16:40:59 +0300 Subject: [PATCH 082/161] added example of NQueens backtracing algorithm using ReadChannel[F] as cps monad --- build.sbt | 2 +- project/build.properties | 2 +- .../src/test/scala/gopher/monads/Queens.scala | 69 +++++++++++++++++++ 3 files changed, 71 insertions(+), 2 deletions(-) create mode 100644 shared/src/test/scala/gopher/monads/Queens.scala diff --git a/build.sbt b/build.sbt index 2d55a156..189a6db6 100644 --- a/build.sbt +++ b/build.sbt @@ -9,7 +9,7 @@ val sharedSettings = Seq( scalaVersion := dottyVersion, name := "scala-gopher", resolvers += "Local Ivy Repository" at "file://"+Path.userHome.absolutePath+"/.ivy2/local", - libraryDependencies += "com.github.rssh" %%% "dotty-cps-async" % "0.6.2", + libraryDependencies += "com.github.rssh" %%% "dotty-cps-async" % "0.7.0-SNAPSHOT", libraryDependencies += "org.scalameta" %%% "munit" % "0.7.25" % Test, testFrameworks += new TestFramework("munit.Framework") ) diff --git a/project/build.properties b/project/build.properties index dbae93bc..f0be67b9 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=1.4.9 +sbt.version=1.5.1 diff --git a/shared/src/test/scala/gopher/monads/Queens.scala b/shared/src/test/scala/gopher/monads/Queens.scala new file mode 100644 index 00000000..0a8f4a17 --- /dev/null +++ b/shared/src/test/scala/gopher/monads/Queens.scala @@ -0,0 +1,69 @@ +package gopher.monadexample +import cps.* +import gopher.* +import munit.* + +import scala.concurrent.* +import scala.concurrent.duration.* +import scala.collection.SortedSet + +import cps.monads.FutureAsyncMonad +//import gopher.monads.given +import gopher.monads.ReadChannelCpsMonad + + +class QueensSuite extends FunSuite { + + import scala.concurrent.ExecutionContext.Implicits.global + given Gopher[Future] = SharedGopherAPI.apply[Future]() + + case class State( + busyRows:Set[Int], + busyColumns:Set[Int], + busyDiagonals:Set[Int], + queens: Set[(Int,Int)] + ); + + val N = 8 + + def putQueen(state:State): ReadChannel[Future,State] = + val ch = makeChannel[State]() + async[Future] { + for{ + i <- 0 until N if !state.busyRows.contains(i) + j <- 0 until N if !state.busyColumns.contains(j) && + !(state.busyDiagonals.contains(i-j)) + } { + val newPos = (i,j) + val nState = state.copy( busyRows = state.busyRows + i, + busyColumns = state.busyColumns + j, + busyDiagonals = state.busyDiagonals + (i-j), + queens = state.queens + newPos ) + ch.write(nState) + } + ch.close() + } + ch + + def solutions(state: State): ReadChannel[Future,State] = + async[[X] =>> ReadChannel[Future,X]] { + if(state.queens.size < 8) then + val nextState = await(putQueen(state)) + await(solutions(nextState)) + else + state + } + + val emptyState = State(Set.empty, Set.empty, Set.empty, Set.empty) + + test("first solution for 8 queens problem") { + async[Future] { + val r = solutions(emptyState).take(1) + assert(!r.isEmpty) + println(r.head.queens) + } + } + + + +} \ No newline at end of file From e5452fbb5fa19f881edfb5b83a469c80f41d5366 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Sun, 9 May 2021 17:44:44 +0300 Subject: [PATCH 083/161] cosmetics --- shared/src/test/scala/gopher/monads/Queens.scala | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/shared/src/test/scala/gopher/monads/Queens.scala b/shared/src/test/scala/gopher/monads/Queens.scala index 0a8f4a17..9a475c96 100644 --- a/shared/src/test/scala/gopher/monads/Queens.scala +++ b/shared/src/test/scala/gopher/monads/Queens.scala @@ -8,8 +8,7 @@ import scala.concurrent.duration.* import scala.collection.SortedSet import cps.monads.FutureAsyncMonad -//import gopher.monads.given -import gopher.monads.ReadChannelCpsMonad +import gopher.monads.given class QueensSuite extends FunSuite { From ff2d1104571104c03311bb75df7f0aec940fc6e9 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Mon, 10 May 2021 06:29:10 +0300 Subject: [PATCH 084/161] fixed error with forgotten check for RL diagonal in 8 queens --- shared/src/test/scala/gopher/monads/Queens.scala | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/shared/src/test/scala/gopher/monads/Queens.scala b/shared/src/test/scala/gopher/monads/Queens.scala index 9a475c96..8304c359 100644 --- a/shared/src/test/scala/gopher/monads/Queens.scala +++ b/shared/src/test/scala/gopher/monads/Queens.scala @@ -19,7 +19,8 @@ class QueensSuite extends FunSuite { case class State( busyRows:Set[Int], busyColumns:Set[Int], - busyDiagonals:Set[Int], + busyLRDiagonals:Set[Int], + busyRLDiagonals:Set[Int], queens: Set[(Int,Int)] ); @@ -31,12 +32,14 @@ class QueensSuite extends FunSuite { for{ i <- 0 until N if !state.busyRows.contains(i) j <- 0 until N if !state.busyColumns.contains(j) && - !(state.busyDiagonals.contains(i-j)) + !state.busyLRDiagonals.contains(i-j) && + !state.busyRLDiagonals.contains(i+j) } { val newPos = (i,j) val nState = state.copy( busyRows = state.busyRows + i, busyColumns = state.busyColumns + j, - busyDiagonals = state.busyDiagonals + (i-j), + busyLRDiagonals = state.busyLRDiagonals + (i-j), + busyRLDiagonals = state.busyRLDiagonals + (i+j), queens = state.queens + newPos ) ch.write(nState) } @@ -53,7 +56,7 @@ class QueensSuite extends FunSuite { state } - val emptyState = State(Set.empty, Set.empty, Set.empty, Set.empty) + val emptyState = State(Set.empty, Set.empty, Set.empty, Set.empty, Set.empty) test("first solution for 8 queens problem") { async[Future] { @@ -64,5 +67,4 @@ class QueensSuite extends FunSuite { } - } \ No newline at end of file From 76403f2ed1ff5d753e8ae37ae9294dadd60e624b Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Mon, 10 May 2021 09:02:27 +0300 Subject: [PATCH 085/161] optimization of 8 queens problen --- .../src/test/scala/gopher/monads/Queens.scala | 33 ++++++++++--------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/shared/src/test/scala/gopher/monads/Queens.scala b/shared/src/test/scala/gopher/monads/Queens.scala index 8304c359..d7d53519 100644 --- a/shared/src/test/scala/gopher/monads/Queens.scala +++ b/shared/src/test/scala/gopher/monads/Queens.scala @@ -21,7 +21,7 @@ class QueensSuite extends FunSuite { busyColumns:Set[Int], busyLRDiagonals:Set[Int], busyRLDiagonals:Set[Int], - queens: Set[(Int,Int)] + queens: Vector[(Int,Int)] ); val N = 8 @@ -29,40 +29,43 @@ class QueensSuite extends FunSuite { def putQueen(state:State): ReadChannel[Future,State] = val ch = makeChannel[State]() async[Future] { - for{ - i <- 0 until N if !state.busyRows.contains(i) - j <- 0 until N if !state.busyColumns.contains(j) && + val i = state.queens.length + if i < N then + for{ + j <- 0 until N if !state.busyColumns.contains(j) && !state.busyLRDiagonals.contains(i-j) && !state.busyRLDiagonals.contains(i+j) - } { - val newPos = (i,j) - val nState = state.copy( busyRows = state.busyRows + i, + } { + val newPos = (i,j) + val nState = state.copy( busyRows = state.busyRows + i, busyColumns = state.busyColumns + j, busyLRDiagonals = state.busyLRDiagonals + (i-j), busyRLDiagonals = state.busyRLDiagonals + (i+j), - queens = state.queens + newPos ) - ch.write(nState) - } + queens = state.queens :+ newPos ) + ch.write(nState) + } ch.close() } ch def solutions(state: State): ReadChannel[Future,State] = async[[X] =>> ReadChannel[Future,X]] { - if(state.queens.size < 8) then + if(state.queens.size < N) then + //println("state:"+state.queens) val nextState = await(putQueen(state)) + //println("next-state:"+state.queens) await(solutions(nextState)) else state } - val emptyState = State(Set.empty, Set.empty, Set.empty, Set.empty, Set.empty) + val emptyState = State(Set.empty, Set.empty, Set.empty, Set.empty, Vector.empty) - test("first solution for 8 queens problem") { + test("two first solution for 8 queens problem") { async[Future] { - val r = solutions(emptyState).take(1) + val r = solutions(emptyState).take(2) assert(!r.isEmpty) - println(r.head.queens) + println(r.map(_.queens)) } } From b3c2dcd30e1e5a8853a4aaa9b206778f2ea7a2b5 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Wed, 12 May 2021 22:37:37 +0300 Subject: [PATCH 086/161] CpsMonadConversion[F[_],G[_]] => Conversion[F[T],G[T]] --- .../main/scala/gopher/monads/ReadChannelCpsMonad.scala | 8 ++------ .../main/scala/gopher/monads/ReadTryChannelCpsMonad.scala | 8 ++++---- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/shared/src/main/scala/gopher/monads/ReadChannelCpsMonad.scala b/shared/src/main/scala/gopher/monads/ReadChannelCpsMonad.scala index 15a89eae..0ad8dd3e 100644 --- a/shared/src/main/scala/gopher/monads/ReadChannelCpsMonad.scala +++ b/shared/src/main/scala/gopher/monads/ReadChannelCpsMonad.scala @@ -6,8 +6,6 @@ import cps._ import gopher.impl._ - - given ReadChannelCpsMonad[F[_]](using Gopher[F]): CpsMonad[ [A] =>> ReadChannel[F,A]] with def pure[T](t:T): ReadChannel[F,T] = @@ -20,11 +18,9 @@ given ReadChannelCpsMonad[F[_]](using Gopher[F]): CpsMonad[ [A] =>> ReadChannel[ new ChFlatMappedReadChannel[F,A,B](fa,f) -given futureToReadChannel[F[_]](using Gopher[F]): CpsMonadConversion[F, [A]=>>ReadChannel[F,A]] with - - def apply[T](m: CpsMonad[F], mg: CpsMonad[[A] =>> ReadChannel[F,A]], ft: F[T]): ReadChannel[F,T] = - futureInput(ft) +given futureToReadChannel[F[_],T](using Gopher[F]): Conversion[F[T], ReadChannel[F,T]] with + def apply(ft: F[T]): ReadChannel[F,T] = futureInput(ft) diff --git a/shared/src/main/scala/gopher/monads/ReadTryChannelCpsMonad.scala b/shared/src/main/scala/gopher/monads/ReadTryChannelCpsMonad.scala index 39350134..8f5cd3cf 100644 --- a/shared/src/main/scala/gopher/monads/ReadTryChannelCpsMonad.scala +++ b/shared/src/main/scala/gopher/monads/ReadTryChannelCpsMonad.scala @@ -57,10 +57,10 @@ given ReadTryChannelCpsMonad[F[_]](using Gopher[F]): CpsAsyncMonad[ [A] =>> Read } -given readChannelToTryReadChannel[F[_]](using Gopher[F]): CpsMonadConversion[ [A]=>>ReadChannel[F,A], [A]=>>ReadChannel[F,Try[A]]] with - - def apply[T](m: CpsMonad[[A]=>>ReadChannel[F,A]], mg: CpsMonad[[A] =>> ReadChannel[F,Try[A]]], ft: ReadChannel[F,T]): ReadChannel[F,Try[T]] = - ft.map(x => Success(x)) +given readChannelToTryReadChannel[F[_],T](using Gopher[F]): Conversion[ ReadChannel[F,T], ReadChannel[F,Try[T]]] with + + def apply(ft: ReadChannel[F,T]): ReadChannel[F,Try[T]] =ft.map(x => Success(x)) + From 245a05d89cf1a0a9280305eb8c68c850f5749c44 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Thu, 13 May 2021 21:45:57 +0300 Subject: [PATCH 087/161] release for scala-3.0.0 --- build.sbt | 10 +++++----- project/plugins.sbt | 1 - 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/build.sbt b/build.sbt index 189a6db6..3eff5a22 100644 --- a/build.sbt +++ b/build.sbt @@ -1,16 +1,16 @@ //val dottyVersion = "3.0.0-RC2-bin-SNAPSHOT" -val dottyVersion = "3.0.0-RC3" +val dottyVersion = "3.0.0" //val dottyVersion = dottyLatestNightlyBuild.get -ThisBuild/version := "2.0.1-RC3" +ThisBuild/version := "2.0.2" val sharedSettings = Seq( organization := "com.github.rssh", scalaVersion := dottyVersion, name := "scala-gopher", resolvers += "Local Ivy Repository" at "file://"+Path.userHome.absolutePath+"/.ivy2/local", - libraryDependencies += "com.github.rssh" %%% "dotty-cps-async" % "0.7.0-SNAPSHOT", - libraryDependencies += "org.scalameta" %%% "munit" % "0.7.25" % Test, + libraryDependencies += "com.github.rssh" %%% "dotty-cps-async" % "0.7.0", + libraryDependencies += "org.scalameta" %%% "munit" % "0.7.26" % Test, testFrameworks += new TestFramework("munit.Framework") ) @@ -32,7 +32,7 @@ lazy val gopher = crossProject(JSPlatform, JVMPlatform) .jvmSettings( scalacOptions ++= Seq( "-unchecked", "-Ycheck:macros", "-uniqid", "-Xprint:types" ), ).jsSettings( - libraryDependencies += ("org.scala-js" %%% "scalajs-java-logging" % "1.0.0").withDottyCompat(scalaVersion.value), + libraryDependencies += ("org.scala-js" %%% "scalajs-java-logging" % "1.0.0").cross(CrossVersion.for3Use2_13), // TODO: switch to ModuleES ? scalaJSLinkerConfig ~= { _.withModuleKind(ModuleKind.CommonJSModule) }, scalaJSUseMainModuleInitializer := true, diff --git a/project/plugins.sbt b/project/plugins.sbt index 6a6ff46e..02f86b3f 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,4 +1,3 @@ -addSbtPlugin("ch.epfl.lamp" % "sbt-dotty" % "0.5.3") addSbtPlugin("com.jsuereth" % "sbt-pgp" % "2.0.2") addSbtPlugin("com.typesafe.sbt" % "sbt-site" % "1.4.0") addSbtPlugin("com.typesafe.sbt" % "sbt-ghpages" % "0.6.3") From 7cbdfa3dee3746604bcb17456b1139bedf8d0596 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Thu, 13 May 2021 21:52:45 +0300 Subject: [PATCH 088/161] updated version in README + phrase abotu handling channel and done.channel in one select. --- README.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 9d771c73..53cc80d7 100644 --- a/README.md +++ b/README.md @@ -4,9 +4,9 @@ ### Dependences: -For scala 3.0.0-RC3: +For scala 3: - libraryDependencies += "com.github.rssh" %% "scala-gopher" % "2.0.1-RC3" + libraryDependencies += "com.github.rssh" %% "scala-gopher" % "2.0.2" For scala2: @@ -231,7 +231,7 @@ val multiplexed = select amap { ## Done signals. - Sometimes it is useful to receive a message when some `ReadChannel` becomes closed. Exists way to receive close notification in selector using `done` pseudo-channel, which is available for each 'normal' channel. When channel is closed, all readers of done channels receive notifications. + Sometimes it is useful to receive a message when some `ReadChannel` becomes closed. Exists a way to receive close notification in selector using `done` pseudo-channel, which is available for each 'normal' channel. When the channel is closed, all readers of done channels receive notifications. ~~~ scala while(!done) @@ -242,6 +242,8 @@ val multiplexed = select amap { } ~~~ + Note, that if we query some channel and it's done channel in the same select, and done channel is not aliased in some vairable, then done handler will be called first after channel close. + # References: ---------------------- From aa5e7e988bcb71868cb3c2d2a3183f9d2eeaf179 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Wed, 19 May 2021 18:27:30 +0300 Subject: [PATCH 089/161] adopted to dotty-cps-async 0.8.0 snapshot --- build.sbt | 4 ++-- .../src/main/scala/gopher/monads/ReadChannelCpsMonad.scala | 4 ++-- .../main/scala/gopher/monads/ReadTryChannelCpsMonad.scala | 5 +++-- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/build.sbt b/build.sbt index 3eff5a22..c026d2dd 100644 --- a/build.sbt +++ b/build.sbt @@ -2,14 +2,14 @@ val dottyVersion = "3.0.0" //val dottyVersion = dottyLatestNightlyBuild.get -ThisBuild/version := "2.0.2" +ThisBuild/version := "2.0.3-SNAPSHOT" val sharedSettings = Seq( organization := "com.github.rssh", scalaVersion := dottyVersion, name := "scala-gopher", resolvers += "Local Ivy Repository" at "file://"+Path.userHome.absolutePath+"/.ivy2/local", - libraryDependencies += "com.github.rssh" %%% "dotty-cps-async" % "0.7.0", + libraryDependencies += "com.github.rssh" %%% "dotty-cps-async" % "0.8.0-SNAPSHOT", libraryDependencies += "org.scalameta" %%% "munit" % "0.7.26" % Test, testFrameworks += new TestFramework("munit.Framework") ) diff --git a/shared/src/main/scala/gopher/monads/ReadChannelCpsMonad.scala b/shared/src/main/scala/gopher/monads/ReadChannelCpsMonad.scala index 0ad8dd3e..98c91f6a 100644 --- a/shared/src/main/scala/gopher/monads/ReadChannelCpsMonad.scala +++ b/shared/src/main/scala/gopher/monads/ReadChannelCpsMonad.scala @@ -18,9 +18,9 @@ given ReadChannelCpsMonad[F[_]](using Gopher[F]): CpsMonad[ [A] =>> ReadChannel[ new ChFlatMappedReadChannel[F,A,B](fa,f) -given futureToReadChannel[F[_],T](using Gopher[F]): Conversion[F[T], ReadChannel[F,T]] with +given futureToReadChannel[F[_]](using Gopher[F]): CpsMonadConversion[F, [A] =>> ReadChannel[F,A]] with - def apply(ft: F[T]): ReadChannel[F,T] = futureInput(ft) + def apply[T](ft: F[T]): ReadChannel[F,T] = futureInput(ft) diff --git a/shared/src/main/scala/gopher/monads/ReadTryChannelCpsMonad.scala b/shared/src/main/scala/gopher/monads/ReadTryChannelCpsMonad.scala index 8f5cd3cf..2516f735 100644 --- a/shared/src/main/scala/gopher/monads/ReadTryChannelCpsMonad.scala +++ b/shared/src/main/scala/gopher/monads/ReadTryChannelCpsMonad.scala @@ -58,9 +58,10 @@ given ReadTryChannelCpsMonad[F[_]](using Gopher[F]): CpsAsyncMonad[ [A] =>> Read -given readChannelToTryReadChannel[F[_],T](using Gopher[F]): Conversion[ ReadChannel[F,T], ReadChannel[F,Try[T]]] with +given readChannelToTryReadChannel[F[_]](using Gopher[F]): + CpsMonadConversion[ [A]=>>ReadChannel[F,A], [A]=>>ReadChannel[F,Try[A]]] with - def apply(ft: ReadChannel[F,T]): ReadChannel[F,Try[T]] =ft.map(x => Success(x)) + def apply[T](ft: ReadChannel[F,T]): ReadChannel[F,Try[T]] = ft.map(x => Success(x)) From 61041faedf2485e29e04184cd9772e969f721f50 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Sun, 23 May 2021 15:08:42 +0300 Subject: [PATCH 090/161] latest published version is 2.0.3 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 53cc80d7..56822878 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ For scala 3: - libraryDependencies += "com.github.rssh" %% "scala-gopher" % "2.0.2" + libraryDependencies += "com.github.rssh" %% "scala-gopher" % "2.0.3" For scala2: From 034ce58fd42ddc60db73c764de0dd4f106f405db Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Sun, 23 May 2021 15:09:28 +0300 Subject: [PATCH 091/161] added versionign policy --- build.sbt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/build.sbt b/build.sbt index c026d2dd..d3ca66d4 100644 --- a/build.sbt +++ b/build.sbt @@ -2,14 +2,15 @@ val dottyVersion = "3.0.0" //val dottyVersion = dottyLatestNightlyBuild.get -ThisBuild/version := "2.0.3-SNAPSHOT" +ThisBuild/version := "2.0.3" +ThisBuild/versionScheme := Some("semver-spec") val sharedSettings = Seq( organization := "com.github.rssh", scalaVersion := dottyVersion, name := "scala-gopher", resolvers += "Local Ivy Repository" at "file://"+Path.userHome.absolutePath+"/.ivy2/local", - libraryDependencies += "com.github.rssh" %%% "dotty-cps-async" % "0.8.0-SNAPSHOT", + libraryDependencies += "com.github.rssh" %%% "dotty-cps-async" % "0.8.1", libraryDependencies += "org.scalameta" %%% "munit" % "0.7.26" % Test, testFrameworks += new TestFramework("munit.Framework") ) From bb27fe7ff23a1d7347d56b01a9ee3b2dcb322e90 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Sun, 30 May 2021 18:45:38 +0300 Subject: [PATCH 092/161] 2.0.4-SNAPSHOT --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index d3ca66d4..fec5eb36 100644 --- a/build.sbt +++ b/build.sbt @@ -2,7 +2,7 @@ val dottyVersion = "3.0.0" //val dottyVersion = dottyLatestNightlyBuild.get -ThisBuild/version := "2.0.3" +ThisBuild/version := "2.0.4-SNAPSHOT" ThisBuild/versionScheme := Some("semver-spec") val sharedSettings = Seq( From 3fba32a09c359f287336b6b9b8c131202cd843fe Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Sun, 30 May 2021 18:50:09 +0300 Subject: [PATCH 093/161] added ReadChannel.unfold fiexe situation, where exception in map is not propagated to reader --- .../src/main/scala/gopher/ReadChannel.scala | 31 ++++++++- .../gopher/impl/FilteredReadChannel.scala | 2 +- .../scala/gopher/impl/MappedReadChannel.scala | 24 ++++--- .../channels/ReadChannelFactoryTest.scala | 68 +++++++++++++++++++ 4 files changed, 113 insertions(+), 12 deletions(-) create mode 100644 shared/src/test/scala/gopher/channels/ReadChannelFactoryTest.scala diff --git a/shared/src/main/scala/gopher/ReadChannel.scala b/shared/src/main/scala/gopher/ReadChannel.scala index d3dc8a86..232bf236 100644 --- a/shared/src/main/scala/gopher/ReadChannel.scala +++ b/shared/src/main/scala/gopher/ReadChannel.scala @@ -5,6 +5,7 @@ import gopher.impl._ import scala.util.Try import scala.util.Success import scala.util.Failure +import scala.util.control.NonFatal import scala.concurrent.duration.Duration import java.util.logging.{Level => LogLevel} @@ -231,7 +232,35 @@ object ReadChannel: def fromValues[F[_],A](values: A*)(using Gopher[F]): ReadChannel[F,A] = fromIterable(values) - + def unfold[S,F[_],A](s:S)(f:S => Option[(A,S)])(using Gopher[F]): ReadChannel[F,A] = + unfoldAsync[S,F,A](s)( state => summon[Gopher[F]].asyncMonad.tryPure(f(state)) ) + + def unfoldAsync[S,F[_],A](s:S)(f:S => F[Option[(A,S)]])(using Gopher[F]): ReadChannel[F,A]= + given asyncMonad: CpsSchedulingMonad[F] = summon[Gopher[F]].asyncMonad + val retval = makeChannel[Try[A]]() + summon[Gopher[F]].spawnAndLogFail(async{ + var done = false + var state = s + try + while(!done) { + await(f(state)) match + case Some((a,next)) => + retval.write(Success(a)) + state = next + case None => + done = true + } + catch + case NonFatal(ex) => + retval.write(Failure(ex)) + finally + retval.close(); + }) + retval.map{ + case Success(x) => x + case Failure(ex) => + throw ex + } end ReadChannel diff --git a/shared/src/main/scala/gopher/impl/FilteredReadChannel.scala b/shared/src/main/scala/gopher/impl/FilteredReadChannel.scala index 41eeca95..4c0a221f 100644 --- a/shared/src/main/scala/gopher/impl/FilteredReadChannel.scala +++ b/shared/src/main/scala/gopher/impl/FilteredReadChannel.scala @@ -68,7 +68,7 @@ class FilteredAsyncReadChannel[F[_],A](internal: ReadChannel[F,A], p: A=>F[Boole if (v) { if (markedUsed.get()) { nested.markUsed() - } + } fun(Success(a)) } else { nested.markFree() diff --git a/shared/src/main/scala/gopher/impl/MappedReadChannel.scala b/shared/src/main/scala/gopher/impl/MappedReadChannel.scala index de8a0fa9..8641d663 100644 --- a/shared/src/main/scala/gopher/impl/MappedReadChannel.scala +++ b/shared/src/main/scala/gopher/impl/MappedReadChannel.scala @@ -11,8 +11,12 @@ class MappedReadChannel[F[_],A, B](internal: ReadChannel[F,A], f: A=> B) extends def wrappedFun(fun: (Try[B] => Unit) ): (Try[A] => Unit) = { case Success(a) => - val b = f(a) - fun(Success(b)) + try + val b = f(a) + fun(Success(b)) + catch + case NonFatal(ex) => + fun(Failure(ex)) case Failure(ex) => fun(Failure(ex)) } @@ -50,14 +54,14 @@ class MappedAsyncReadChannel[F[_],A, B](internal: ReadChannel[F,A], f: A=> F[B]) def wrappedFun(fun: (Try[B] => Unit) ): (Try[A] => Unit) = { case Success(a) => - try{ - gopherApi.spawnAndLogFail( - asyncMonad.mapTry(f(a))(fun) - ) - }catch{ - case NonFatal(ex) => - fun(Failure(ex)) - } + gopherApi.spawnAndLogFail( + try + asyncMonad.mapTry(f(a))(fun) + catch + case NonFatal(ex) => + fun(Failure(ex)) + asyncMonad.pure(()) + ) case Failure(ex) => fun(Failure(ex)) } diff --git a/shared/src/test/scala/gopher/channels/ReadChannelFactoryTest.scala b/shared/src/test/scala/gopher/channels/ReadChannelFactoryTest.scala new file mode 100644 index 00000000..527cda0b --- /dev/null +++ b/shared/src/test/scala/gopher/channels/ReadChannelFactoryTest.scala @@ -0,0 +1,68 @@ +package gopher.channels + +import gopher._ +import cps._ +import munit._ + +import scala.concurrent.ExecutionContext.Implicits.global +import scala.concurrent.{Channel=>_,_} +import scala.concurrent.duration._ + +import cps.monads.FutureAsyncMonad + +class ReadChannelFactoryTest extends FunSuite { + + given Gopher[Future] = Gopher[Future]() + + + test("unfoldAsync produce stream simple") { + val ch = ReadChannel.unfoldAsync(0){ + (x: Int) => + if (x > 10) then + Future successful None + else + Future successful Some(x,x+1) + } + + ch.atake(20).map{ values => + assert(values(0) == 0) + assert(values(1) == 1) + assert(values(2) == 2) + assert(values.size == 11) + } + + } + + + test("unfoldAsync prodce stream with error") { + val ch = ReadChannel.unfoldAsync(0){ + (x: Int) => + if (x > 3) then + Future failed new RuntimeException("state is too big") + else + Future successful Some(x,x+1) + } + + async { + val r0 = ch.read() + assert(r0 == 0) + val r1 = ch.read() + assert(r1 == 1) + val r2 = ch.read() + assert(r2 == 2) + val r3 = ch.read() + assert(r3 == 3) + var wasTooBig = false + try { + val r4 = ch.read() + }catch{ + case e: RuntimeException => + wasTooBig = true + } + assert(wasTooBig) + } + + + } + +} From a179bae99f7aed42ad3d12785f7d53c754f4de9f Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Fri, 11 Jun 2021 17:45:50 +0300 Subject: [PATCH 094/161] scaladoc added --- build.sbt | 9 ++++++-- project/plugins.sbt | 2 +- .../src/main/scala/gopher/ReadChannel.scala | 5 +++- shared/src/main/scala/gopher/Select.scala | 23 +++++++++++++++---- 4 files changed, 31 insertions(+), 8 deletions(-) diff --git a/build.sbt b/build.sbt index fec5eb36..9ece1d8f 100644 --- a/build.sbt +++ b/build.sbt @@ -19,10 +19,12 @@ lazy val root = project .in(file(".")) .aggregate(gopher.js, gopher.jvm) .settings( - Sphinx / sourceDirectory := baseDirectory.value / "docs", git.remoteRepo := "git@github.com:rssh/scala-gopher.git", + SiteScaladocPlugin.scaladocSettings(GopherJVM, gopher.jvm / Compile / packageDoc / mappings, "api/jvm"), + SiteScaladocPlugin.scaladocSettings(GopherJS, gopher.js / Compile / packageDoc / mappings, "api/js"), + siteDirectory := baseDirectory.value / "target" / "site", publishArtifact := false, - ).enablePlugins(GhpagesPlugin) + ).enablePlugins(GhpagesPlugin, SiteScaladocPlugin) @@ -30,6 +32,7 @@ lazy val gopher = crossProject(JSPlatform, JVMPlatform) .in(file(".")) .settings(sharedSettings) .disablePlugins(SitePlugin) + .disablePlugins(SitePreviewPlugin) .jvmSettings( scalacOptions ++= Seq( "-unchecked", "-Ycheck:macros", "-uniqid", "-Xprint:types" ), ).jsSettings( @@ -39,3 +42,5 @@ lazy val gopher = crossProject(JSPlatform, JVMPlatform) scalaJSUseMainModuleInitializer := true, ) +lazy val GopherJVM = config("gopher.jvm") +lazy val GopherJS = config("gopher.js") diff --git a/project/plugins.sbt b/project/plugins.sbt index 02f86b3f..9ed7f66a 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,5 +1,5 @@ addSbtPlugin("com.jsuereth" % "sbt-pgp" % "2.0.2") -addSbtPlugin("com.typesafe.sbt" % "sbt-site" % "1.4.0") +addSbtPlugin("com.typesafe.sbt" % "sbt-site" % "1.4.1") addSbtPlugin("com.typesafe.sbt" % "sbt-ghpages" % "0.6.3") addSbtPlugin("org.portable-scala" % "sbt-scalajs-crossproject" % "1.0.0") addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.5.1") diff --git a/shared/src/main/scala/gopher/ReadChannel.scala b/shared/src/main/scala/gopher/ReadChannel.scala index 232bf236..d1b8b5a6 100644 --- a/shared/src/main/scala/gopher/ReadChannel.scala +++ b/shared/src/main/scala/gopher/ReadChannel.scala @@ -10,7 +10,10 @@ import scala.concurrent.duration.Duration import java.util.logging.{Level => LogLevel} - +/** + * ReadChannel: Interface providing reading API. + * + **/ trait ReadChannel[F[_], A]: thisReadChannel => diff --git a/shared/src/main/scala/gopher/Select.scala b/shared/src/main/scala/gopher/Select.scala index 6bf9f88f..ba22e154 100644 --- a/shared/src/main/scala/gopher/Select.scala +++ b/shared/src/main/scala/gopher/Select.scala @@ -6,9 +6,26 @@ import scala.quoted._ import scala.compiletime._ import scala.concurrent.duration._ - +/** Organize waiting for read/write from multiple async channels + * + * Gopher[F] provide a function `select` of this type. + */ class Select[F[_]](api: Gopher[F]): + /** wait until some channels from the list in pf . + * + *```Scala + *async{ + * .... + * select { + * case vx:xChannel.read => doSomethingWithX + * case vy:yChannel.write if (vy == valueToWrite) => doSomethingAfterWrite(vy) + * case t: Time.after if (t == 1.minute) => processTimeout + * } + * ... + *} + *``` + */ transparent inline def apply[A](inline pf: PartialFunction[Any,A]): A = ${ Select.onceImpl[F,A]('pf, 'api ) @@ -51,9 +68,7 @@ class Select[F[_]](api: Gopher[F]): def afold_async[S](s0:S)(step: S => F[S | SelectFold.Done[S]]) : F[S] = fold_async(s0)(step) - - //def map[A](step: PartialFunction[SelectGroup[F,A],A|SelectFold.Done[Unit]]): ReadChannel[F,A] = - + def map[A](step: SelectGroup[F,A] => A): ReadChannel[F,A] = mapAsync[A](x => api.asyncMonad.pure(step(x))) From ee2a04e4bf013a127ddc90556cf33bf0ebac9e9a Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Mon, 28 Jun 2021 11:23:59 +0300 Subject: [PATCH 095/161] updated to scala 3.0.1-RC2 (with fixed https://github.com/lampepfl/dotty/issues/12791 ) --- build.sbt | 2 +- shared/src/main/scala/gopher/Select.scala | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/build.sbt b/build.sbt index 9ece1d8f..e8612102 100644 --- a/build.sbt +++ b/build.sbt @@ -1,5 +1,5 @@ //val dottyVersion = "3.0.0-RC2-bin-SNAPSHOT" -val dottyVersion = "3.0.0" +val dottyVersion = "3.0.1-RC2" //val dottyVersion = dottyLatestNightlyBuild.get ThisBuild/version := "2.0.4-SNAPSHOT" diff --git a/shared/src/main/scala/gopher/Select.scala b/shared/src/main/scala/gopher/Select.scala index ba22e154..a37120cc 100644 --- a/shared/src/main/scala/gopher/Select.scala +++ b/shared/src/main/scala/gopher/Select.scala @@ -7,9 +7,9 @@ import scala.compiletime._ import scala.concurrent.duration._ /** Organize waiting for read/write from multiple async channels - * - * Gopher[F] provide a function `select` of this type. - */ + * + * Gopher[F] provide a function `select` of this type. + */ class Select[F[_]](api: Gopher[F]): /** wait until some channels from the list in pf . From 438880490b6bd092473852a486746abbc86db0e5 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Tue, 13 Jul 2021 19:30:26 +0300 Subject: [PATCH 096/161] adopted do scala3.0.1 and dotty-cps-async 0.9.0 --- build.sbt | 6 +- project/build.properties | 2 +- shared/src/main/scala/gopher/Select.scala | 290 +--------------- .../src/main/scala/gopher/SelectForever.scala | 11 +- .../src/main/scala/gopher/SelectGroup.scala | 4 +- shared/src/main/scala/gopher/SelectLoop.scala | 2 +- .../src/main/scala/gopher/SelectMacro.scala | 316 ++++++++++++++++++ .../channels/ForeverTerminationSuite.scala | 9 +- 8 files changed, 353 insertions(+), 287 deletions(-) create mode 100644 shared/src/main/scala/gopher/SelectMacro.scala diff --git a/build.sbt b/build.sbt index e8612102..d9494fe9 100644 --- a/build.sbt +++ b/build.sbt @@ -1,8 +1,8 @@ //val dottyVersion = "3.0.0-RC2-bin-SNAPSHOT" -val dottyVersion = "3.0.1-RC2" +val dottyVersion = "3.0.1" //val dottyVersion = dottyLatestNightlyBuild.get -ThisBuild/version := "2.0.4-SNAPSHOT" +ThisBuild/version := "2.0.4" ThisBuild/versionScheme := Some("semver-spec") val sharedSettings = Seq( @@ -10,7 +10,7 @@ val sharedSettings = Seq( scalaVersion := dottyVersion, name := "scala-gopher", resolvers += "Local Ivy Repository" at "file://"+Path.userHome.absolutePath+"/.ivy2/local", - libraryDependencies += "com.github.rssh" %%% "dotty-cps-async" % "0.8.1", + libraryDependencies += "com.github.rssh" %%% "dotty-cps-async" % "0.9.0", libraryDependencies += "org.scalameta" %%% "munit" % "0.7.26" % Test, testFrameworks += new TestFramework("munit.Framework") ) diff --git a/project/build.properties b/project/build.properties index f0be67b9..10fd9eee 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=1.5.1 +sbt.version=1.5.5 diff --git a/shared/src/main/scala/gopher/Select.scala b/shared/src/main/scala/gopher/Select.scala index a37120cc..f94531f7 100644 --- a/shared/src/main/scala/gopher/Select.scala +++ b/shared/src/main/scala/gopher/Select.scala @@ -28,9 +28,13 @@ class Select[F[_]](api: Gopher[F]): */ transparent inline def apply[A](inline pf: PartialFunction[Any,A]): A = ${ - Select.onceImpl[F,A]('pf, 'api ) + SelectMacro.onceImpl[F,A]('pf, 'api ) } + /*** + * create select groop + *@see [gopher.SelectGroup] + **/ def group[S]: SelectGroup[F,S] = new SelectGroup[F,S](api) def once[S]: SelectGroup[F,S] = new SelectGroup[F,S](api) @@ -92,287 +96,25 @@ class Select[F[_]](api: Gopher[F]): } r - def forever: SelectForever[F] = new SelectForever[F](api ) + /** + * create forever runner. + **/ + def forever: SelectForever[F] = new SelectForever[F](api) + /** + * run forever expression in `pf`, return + **/ transparent inline def aforever(inline pf: PartialFunction[Any,Unit]): F[Unit] = - async(using api.asyncMonad).apply { - val runner = new SelectForever[F](api) - runner.apply(pf) - } + ${ SelectMacro.aforeverImpl('pf, 'api) } - def aforever_async(pf: PartialFunction[Any,F[Unit]]): F[Unit] = + /* + transparent inline def aforever_async(inline pf: PartialFunction[Any,F[Unit]]): F[Unit] = given CpsSchedulingMonad[F] = api.asyncMonad async(using api.asyncMonad).apply { val runner = new SelectForever[F](api) runner.applyAsync(pf) } - + */ -object Select: - - import cps.forest.TransformUtil - - sealed trait SelectGroupExpr[F[_],S, R]: - def toExprOf[X <: SelectListeners[F,S, R]]: Expr[X] - - sealed trait SelectorCaseExpr[F[_]:Type, S:Type, R:Type]: - type Monad[X] = F[X] - def appended[L <: SelectListeners[F,S,R] : Type](base: Expr[L])(using Quotes): Expr[L] - - case class ReadExpression[F[_]:Type, A:Type, S:Type, R:Type](ch: Expr[ReadChannel[F,A]], f: Expr[A => S], isDone: Boolean) extends SelectorCaseExpr[F,S,R]: - def appended[L <: SelectListeners[F,S,R]: Type](base: Expr[L])(using Quotes): Expr[L] = - '{ $base.onRead($ch)($f) } - - case class WriteExpression[F[_]:Type, A:Type, S:Type, R:Type](ch: Expr[WriteChannel[F,A]], a: Expr[A], f: Expr[A => S]) extends SelectorCaseExpr[F,S,R]: - def appended[L <: SelectListeners[F,S,R]: Type](base: Expr[L])(using Quotes): Expr[L] = - '{ $base.onWrite($ch,$a)($f) } - - case class TimeoutExpression[F[_]:Type,S:Type, R:Type](t: Expr[FiniteDuration], f: Expr[ FiniteDuration => S ]) extends SelectorCaseExpr[F,S,R]: - def appended[L <: SelectListeners[F,S,R]: Type](base: Expr[L])(using Quotes): Expr[L] = - '{ $base.onTimeout($t)($f) } - - case class DoneExression[F[_]:Type, A:Type, S:Type, R:Type](ch: Expr[ReadChannel[F,A]], f: Expr[Unit=>S]) extends SelectorCaseExpr[F,S,R]: - def appended[L <: SelectListeners[F,S,R]: Type](base: Expr[L])(using Quotes): Expr[L] = - '{ $base.onRead($ch.done)($f) } - - def selectListenerBuilder[F[_]:Type, S:Type, R:Type, L <: SelectListeners[F,S,R]:Type]( - constructor: Expr[L], caseDefs: List[SelectorCaseExpr[F,S,R]], api:Expr[Gopher[F]])(using Quotes): Expr[R] = - val s0 = constructor - val g = caseDefs.foldLeft(s0){(s,e) => - e.appended(s) - } - // dotty bug if g.run - val r = '{ await($g.runAsync())(using ${api}.asyncMonad) } - r.asExprOf[R] - - - def onceImpl[F[_]:Type, A:Type](pf: Expr[PartialFunction[Any,A]], api: Expr[Gopher[F]])(using Quotes): Expr[A] = - def builder(caseDefs: List[SelectorCaseExpr[F,A,A]]):Expr[A] = { - val s0 = '{ - new SelectGroup[F,A]($api) - } - selectListenerBuilder(s0, caseDefs, api) - } - runImpl(builder, pf) - - def loopImpl[F[_]:Type](pf: Expr[PartialFunction[Any,Boolean]], api: Expr[Gopher[F]])(using Quotes): Expr[Unit] = - def builder(caseDefs: List[SelectorCaseExpr[F,Boolean,Unit]]):Expr[Unit] = { - val s0 = '{ - new SelectLoop[F]($api) - } - selectListenerBuilder(s0, caseDefs, api) - } - runImpl( builder, pf) - - - def foreverImpl[F[_]:Type](pf: Expr[PartialFunction[Any,Unit]], api:Expr[Gopher[F]])(using Quotes): Expr[Unit] = - def builder(caseDefs: List[SelectorCaseExpr[F,Unit,Unit]]):Expr[Unit] = { - val s0 = '{ - new SelectForever[F]($api) - } - selectListenerBuilder(s0, caseDefs, api) - } - runImpl(builder, pf) - - - - def runImpl[F[_]:Type, A:Type,B :Type](builder: List[SelectorCaseExpr[F,A,B]]=>Expr[B], - pf: Expr[PartialFunction[Any,A]])(using Quotes): Expr[B] = - import quotes.reflect._ - runImplTree[F,A,B](builder, pf.asTerm) - - def runImplTree[F[_]:Type, A:Type, B:Type](using Quotes)( - builder: List[SelectorCaseExpr[F,A,B]] => Expr[B], - pf: quotes.reflect.Term - ): Expr[B] = - import quotes.reflect._ - pf match - case Lambda(valDefs, body) => - runImplTree[F,A,B](builder, body) - case Inlined(_,List(),body) => - runImplTree[F,A,B](builder, body) - case Match(scrutinee,cases) => - //val caseExprs = cases map(x => parseCaseDef[F,A](x)) - //if (caseExprs.find(_.isInstanceOf[DefaultExpression[?]]).isDefined) { - // report.error("default is not supported") - //} - val unorderedCases = cases.map(parseCaseDef[F,A,B](_)) - // done should be - val (isDone,notDone) = unorderedCases.partition{ x => - x match - case DoneExression(_,_) => true - case ReadExpression(_,_,isDone) => isDone - case _ => false - } - val doneFirstCases = isDone ++ notDone - builder(doneFirstCases) - - - def parseCaseDef[F[_]:Type,S:Type,R:Type](using Quotes)(caseDef: quotes.reflect.CaseDef): SelectorCaseExpr[F,S,R] = - import quotes.reflect._ - - val caseDefGuard = parseCaseDefGuard(caseDef) - - def handleRead(bind: Bind, valName: String, channel:Term, tp:TypeRepr): SelectorCaseExpr[F,S,R] = - val readFun = makeLambda(valName,tp,bind.symbol,caseDef.rhs) - if (channel.tpe <:< TypeRepr.of[ReadChannel[F,?]]) - tp.asType match - case '[a] => - val isDone = channel match - case quotes.reflect.Select(ch1,"done") if (ch1.tpe <:< TypeRepr.of[ReadChannel[F,?]]) => true - case _ => false - ReadExpression(channel.asExprOf[ReadChannel[F,a]],readFun.asExprOf[a=>S],isDone) - case _ => - reportError("can't determinate read type", caseDef.pattern.asExpr) - else - reportError("read pattern is not a read channel", channel.asExpr) - - def handleWrite(bind: Bind, valName: String, channel:Term, tp:TypeRepr): SelectorCaseExpr[F,S,R] = - val writeFun = makeLambda(valName,tp, bind.symbol, caseDef.rhs) - val e = caseDefGuard.getOrElse(valName, - reportError(s"not found binding ${valName} in write condition", channel.asExpr) - ) - if (channel.tpe <:< TypeRepr.of[WriteChannel[F,?]]) then - tp.asType match - case '[a] => - WriteExpression(channel.asExprOf[WriteChannel[F,a]],e.asExprOf[a], writeFun.asExprOf[a=>S]) - case _ => - reportError("Can't determinate type of write", caseDef.pattern.asExpr) - else - reportError("Write channel expected", channel.asExpr) - - def extractType[F[_]:Type](name: "read"|"write", channelTerm: Term, pat: Tree): TypeRepr = - import quotes.reflect._ - pat match - case Typed(_,tp) => tp.tpe - case _ => - TypeSelect(channelTerm,name).tpe - - - caseDef.pattern match - case Inlined(_,List(),body) => - parseCaseDef(CaseDef(body, caseDef.guard, caseDef.rhs)) - case b@Bind(v, tp@Typed(expr, TypeSelect(ch,"read"))) => - handleRead(b,v,ch,tp.tpe) - case b@Bind(v, tp@Typed(expr, Annotated(TypeSelect(ch,"read"),_))) => - handleRead(b,v,ch,tp.tpe) - case tp@Typed(expr, TypeSelect(ch,"read")) => - // todo: introduce 'dummy' val - reportError("binding var in read expression is mandatory", caseDef.pattern.asExpr) - case b@Bind(v, tp@Typed(expr, TypeSelect(ch,"write"))) => - handleWrite(b,v,ch,tp.tpe) - case b@Bind(v, tp@Typed(expr, Annotated(TypeSelect(ch,"write"),_))) => - handleWrite(b,v,ch,tp.tpe) - case b@Bind(v, tp@Typed(expr, TypeSelect(ch,"after"))) => - val timeoutFun = makeLambda(v, tp.tpe, b.symbol, caseDef.rhs) - val e = caseDefGuard.getOrElse(v, reportError(s"can't find condifion for $v",caseDef.pattern.asExpr)) - if (ch.tpe <:< TypeRepr.of[gopher.Time] || ch.tpe <:< TypeRepr.of[gopher.Time.type]) - TimeoutExpression(e.asExprOf[FiniteDuration], timeoutFun.asExprOf[FiniteDuration => S]) - else - reportError(s"Expected Time, we have ${ch.show}", ch.asExpr) - case b@Bind(v, tp@Typed(expr, TypeSelect(ch,"done"))) => - val readFun = makeLambda(v,tp.tpe,b.symbol,caseDef.rhs) - tp.tpe.asType match - case '[a] => - if (ch.tpe <:< TypeRepr.of[ReadChannel[F,a]]) then - DoneExression(ch.asExprOf[ReadChannel[F,a]],readFun.asExprOf[Unit=>S]) - else - reportError("done base is not a read channel", ch.asExpr) - case _ => - reportError("can't determinate read type", caseDef.pattern.asExpr) - case pat@Unapply(TypeApply(quotes.reflect.Select( - quotes.reflect.Select(chobj,nameReadOrWrite), - "unapply"),targs), - impl,List(b@Bind(e,ePat),Bind(ch,chPat))) => - if (chobj.tpe == '{gopher.Channel}.asTerm.tpe) - val chExpr = caseDefGuard.getOrElse(ch,reportError(s"select condition for ${ch} is not found",caseDef.pattern.asExpr)) - nameReadOrWrite match - case "Read" => - val elementType = extractType("read",chExpr, ePat) - handleRead(b,e,chExpr,elementType) - case "Write" => - val elementType = extractType("write",chExpr, ePat) - handleWrite(b,e,chExpr,elementType) - case _ => - reportError(s"Read or Write expected, we have ${nameReadOrWrite}", caseDef.pattern.asExpr) - else - reportError("Incorrect select pattern, expected or x:channel.{read,write} or Channel.{Read,Write}",chobj.asExpr) - case _ => - report.error( - s""" - expected one of: - v: channel.read - v: channel.write if v == expr - v: Time.after if v == expr - we have - ${caseDef.pattern.show} - (tree: ${caseDef.pattern}) - """, caseDef.pattern.asExpr) - reportError(s"unparsed caseDef pattern: ${caseDef.pattern}", caseDef.pattern.asExpr) - - end parseCaseDef - - - def parseCaseDefGuard(using Quotes)(caseDef: quotes.reflect.CaseDef): Map[String,quotes.reflect.Term] = - import quotes.reflect._ - caseDef.guard match - case Some(condition) => - parseSelectCondition(condition, Map.empty) - case None => - Map.empty - - - def parseSelectCondition(using Quotes)(condition: quotes.reflect.Term, - entries:Map[String,quotes.reflect.Term]): Map[String,quotes.reflect.Term] = - import quotes.reflect._ - condition match - case Apply(quotes.reflect.Select(Ident(v1),"=="),List(expr)) => - entries.updated(v1, expr) - case Apply(quotes.reflect.Select(frs, "&&" ), List(snd)) => - parseSelectCondition(snd, parseSelectCondition(frs, entries)) - case _ => - reportError( - s"""Invalid select guard form, expected one of - channelName == channelEpxr - writeBind == writeExpresion - condition && condition - we have - ${condition.show} - """, - condition.asExpr) - - - def makeLambda(using Quotes)(argName: String, - argType: quotes.reflect.TypeRepr, - oldArgSymbol: quotes.reflect.Symbol, - body: quotes.reflect.Term): quotes.reflect.Term = - import quotes.reflect._ - val widenReturnType = TransformUtil.veryWiden(body.tpe) - val mt = MethodType(List(argName))(_ => List(argType.widen), _ => widenReturnType) - Lambda(Symbol.spliceOwner, mt, (owner,args) => - substIdent(body,oldArgSymbol, args.head.asInstanceOf[Term], owner).changeOwner(owner)) - - - def substIdent(using Quotes)(term: quotes.reflect.Term, - fromSym: quotes.reflect.Symbol, - toTerm: quotes.reflect.Term, - owner: quotes.reflect.Symbol): quotes.reflect.Term = - import quotes.reflect._ - val argTransformer = new TreeMap() { - override def transformTerm(tree: Term)(owner: Symbol):Term = - tree match - case Ident(name) if tree.symbol == fromSym => toTerm - case _ => super.transformTerm(tree)(owner) - } - argTransformer.transformTerm(term)(owner) - - - def reportError(message: String, posExpr: Expr[?])(using Quotes): Nothing = - import quotes.reflect._ - report.error(message, posExpr) - throw new RuntimeException(s"Error in macro: $message") - - - diff --git a/shared/src/main/scala/gopher/SelectForever.scala b/shared/src/main/scala/gopher/SelectForever.scala index eb3d8f32..bf26c1c1 100644 --- a/shared/src/main/scala/gopher/SelectForever.scala +++ b/shared/src/main/scala/gopher/SelectForever.scala @@ -6,16 +6,21 @@ import scala.compiletime._ import scala.concurrent.duration._ +/** + * forever Apply + **/ class SelectForever[F[_]](api: Gopher[F]) extends SelectGroupBuilder[F,Unit, Unit](api): transparent inline def apply(inline pf: PartialFunction[Any,Unit]): Unit = ${ - Select.foreverImpl('pf,'api) + SelectMacro.foreverImpl('pf,'api) } - transparent inline def applyAsync(inline pf: PartialFunction[Any,F[Unit]]): Unit = - ??? + transparent inline def applyAsync(inline pf: PartialFunction[Any,Unit]): F[Unit] = + ${ + SelectMacro.aforeverImpl('pf, 'api) + } def runAsync(): F[Unit] = given CpsSchedulingMonad[F] = api.asyncMonad diff --git a/shared/src/main/scala/gopher/SelectGroup.scala b/shared/src/main/scala/gopher/SelectGroup.scala index 3d62d7e0..a1cbaa98 100644 --- a/shared/src/main/scala/gopher/SelectGroup.scala +++ b/shared/src/main/scala/gopher/SelectGroup.scala @@ -65,12 +65,12 @@ class SelectGroup[F[_], S](api: Gopher[F]) extends SelectListeners[F,S,S]: transparent inline def apply(inline pf: PartialFunction[Any,S]): S = ${ - Select.onceImpl[F,S]('pf, 'api ) + SelectMacro.onceImpl[F,S]('pf, 'api ) } transparent inline def select(inline pf: PartialFunction[Any,S]): S = ${ - Select.onceImpl[F,S]('pf, 'api ) + SelectMacro.onceImpl[F,S]('pf, 'api ) } /** diff --git a/shared/src/main/scala/gopher/SelectLoop.scala b/shared/src/main/scala/gopher/SelectLoop.scala index d97b5207..c02a1f3c 100644 --- a/shared/src/main/scala/gopher/SelectLoop.scala +++ b/shared/src/main/scala/gopher/SelectLoop.scala @@ -13,7 +13,7 @@ class SelectLoop[F[_]](api: Gopher[F]) extends SelectGroupBuilder[F,Boolean, Uni transparent inline def apply(inline pf: PartialFunction[Any,Boolean]): Unit = ${ - Select.loopImpl[F]('pf, 'api ) + SelectMacro.loopImpl[F]('pf, 'api ) } def runAsync(): F[Unit] = diff --git a/shared/src/main/scala/gopher/SelectMacro.scala b/shared/src/main/scala/gopher/SelectMacro.scala new file mode 100644 index 00000000..5bcd5863 --- /dev/null +++ b/shared/src/main/scala/gopher/SelectMacro.scala @@ -0,0 +1,316 @@ +package gopher + + +import cps._ + +import scala.quoted._ +import scala.compiletime._ +import scala.concurrent.duration._ + + + + +object SelectMacro: + + import cps.macros.forest.TransformUtil + + sealed trait SelectGroupExpr[F[_],S, R]: + def toExprOf[X <: SelectListeners[F,S, R]]: Expr[X] + + sealed trait SelectorCaseExpr[F[_]:Type, S:Type, R:Type]: + type Monad[X] = F[X] + def appended[L <: SelectListeners[F,S,R] : Type](base: Expr[L])(using Quotes): Expr[L] + + case class ReadExpression[F[_]:Type, A:Type, S:Type, R:Type](ch: Expr[ReadChannel[F,A]], f: Expr[A => S], isDone: Boolean) extends SelectorCaseExpr[F,S,R]: + def appended[L <: SelectListeners[F,S,R]: Type](base: Expr[L])(using Quotes): Expr[L] = + '{ $base.onRead($ch)($f) } + + case class WriteExpression[F[_]:Type, A:Type, S:Type, R:Type](ch: Expr[WriteChannel[F,A]], a: Expr[A], f: Expr[A => S]) extends SelectorCaseExpr[F,S,R]: + def appended[L <: SelectListeners[F,S,R]: Type](base: Expr[L])(using Quotes): Expr[L] = + '{ $base.onWrite($ch,$a)($f) } + + case class TimeoutExpression[F[_]:Type,S:Type, R:Type](t: Expr[FiniteDuration], f: Expr[ FiniteDuration => S ]) extends SelectorCaseExpr[F,S,R]: + def appended[L <: SelectListeners[F,S,R]: Type](base: Expr[L])(using Quotes): Expr[L] = + '{ $base.onTimeout($t)($f) } + + case class DoneExression[F[_]:Type, A:Type, S:Type, R:Type](ch: Expr[ReadChannel[F,A]], f: Expr[Unit=>S]) extends SelectorCaseExpr[F,S,R]: + def appended[L <: SelectListeners[F,S,R]: Type](base: Expr[L])(using Quotes): Expr[L] = + '{ $base.onRead($ch.done)($f) } + + + def selectListenerBuilder1[F[_]:Type, S:Type, R:Type, L <: SelectListeners[F,S,R]:Type]( + constructor: Expr[L], + caseDefs: List[SelectorCaseExpr[F,S,R]])(using Quotes): Expr[L] = + val s0 = constructor + caseDefs.foldLeft(s0){(s,e) => + e.appended(s) + } + + + def buildSelectListenerRun[F[_]:Type, S:Type, R:Type, L <: SelectListeners[F,S,R]:Type]( + constructor: Expr[L], + caseDefs: List[SelectorCaseExpr[F,S,R]], + api:Expr[Gopher[F]])(using Quotes): Expr[R] = + val g = selectListenerBuilder1(constructor, caseDefs) + // dotty bug if g.run + val r = '{ await($g.runAsync())(using ${api}.asyncMonad) } + r.asExprOf[R] + + def buildSelectListenerRunAsync[F[_]:Type, S:Type, R:Type, L <: SelectListeners[F,S,R]:Type]( + constructor: Expr[L], + caseDefs: List[SelectorCaseExpr[F,S,R]], + api:Expr[Gopher[F]])(using Quotes): Expr[F[R]] = + val g = selectListenerBuilder1(constructor, caseDefs) + // dotty bug if g.run + val r = '{ $g.runAsync() } + r.asExprOf[F[R]] + + + + def onceImpl[F[_]:Type, A:Type](pf: Expr[PartialFunction[Any,A]], api: Expr[Gopher[F]])(using Quotes): Expr[A] = + def builder(caseDefs: List[SelectorCaseExpr[F,A,A]]):Expr[A] = { + val s0 = '{ + new SelectGroup[F,A]($api) + } + buildSelectListenerRun(s0, caseDefs, api) + } + runImpl(builder, pf) + + def loopImpl[F[_]:Type](pf: Expr[PartialFunction[Any,Boolean]], api: Expr[Gopher[F]])(using Quotes): Expr[Unit] = + def builder(caseDefs: List[SelectorCaseExpr[F,Boolean,Unit]]):Expr[Unit] = { + val s0 = '{ + new SelectLoop[F]($api) + } + buildSelectListenerRun(s0, caseDefs, api) + } + runImpl( builder, pf) + + + def foreverImpl[F[_]:Type](pf: Expr[PartialFunction[Any,Unit]], api:Expr[Gopher[F]])(using Quotes): Expr[Unit] = + def builder(caseDefs: List[SelectorCaseExpr[F,Unit,Unit]]):Expr[Unit] = { + val s0 = '{ + new SelectForever[F]($api) + } + buildSelectListenerRun(s0, caseDefs, api) + } + runImpl(builder, pf) + + def aforeverImpl[F[_]:Type](pf: Expr[PartialFunction[Any,Unit]], api:Expr[Gopher[F]])(using Quotes): Expr[F[Unit]] = + import quotes.reflect._ + def builder(caseDefs: List[SelectorCaseExpr[F,Unit,Unit]]):Expr[F[Unit]] = { + val s0 = '{ + new SelectForever[F]($api) + } + buildSelectListenerRunAsync(s0, caseDefs, api) + } + runImplTree(builder, pf.asTerm) + + + def runImpl[F[_]:Type, A:Type,B :Type](builder: List[SelectorCaseExpr[F,A,B]]=>Expr[B], + pf: Expr[PartialFunction[Any,A]])(using Quotes): Expr[B] = + import quotes.reflect._ + runImplTree[F,A,B,B](builder, pf.asTerm) + + def runImplTree[F[_]:Type, A:Type, B:Type, C:Type](using Quotes)( + builder: List[SelectorCaseExpr[F,A,B]] => Expr[C], + pf: quotes.reflect.Term + ): Expr[C] = + import quotes.reflect._ + pf match + case Lambda(valDefs, body) => + runImplTree[F,A,B,C](builder, body) + case Inlined(_,List(),body) => + runImplTree[F,A,B,C](builder, body) + case Match(scrutinee,cases) => + //val caseExprs = cases map(x => parseCaseDef[F,A](x)) + //if (caseExprs.find(_.isInstanceOf[DefaultExpression[?]]).isDefined) { + // report.error("default is not supported") + //} + val unorderedCases = cases.map(parseCaseDef[F,A,B](_)) + // done should be + val (isDone,notDone) = unorderedCases.partition{ x => + x match + case DoneExression(_,_) => true + case ReadExpression(_,_,isDone) => isDone + case _ => false + } + val doneFirstCases = isDone ++ notDone + builder(doneFirstCases) + + + def parseCaseDef[F[_]:Type,S:Type,R:Type](using Quotes)(caseDef: quotes.reflect.CaseDef): SelectorCaseExpr[F,S,R] = + import quotes.reflect._ + + val caseDefGuard = parseCaseDefGuard(caseDef) + + def handleRead(bind: Bind, valName: String, channel:Term, tp:TypeRepr): SelectorCaseExpr[F,S,R] = + val readFun = makeLambda(valName,tp,bind.symbol,caseDef.rhs) + if (channel.tpe <:< TypeRepr.of[ReadChannel[F,?]]) + tp.asType match + case '[a] => + val isDone = channel match + case quotes.reflect.Select(ch1,"done") if (ch1.tpe <:< TypeRepr.of[ReadChannel[F,?]]) => true + case _ => false + ReadExpression(channel.asExprOf[ReadChannel[F,a]],readFun.asExprOf[a=>S],isDone) + case _ => + reportError("can't determinate read type", caseDef.pattern.asExpr) + else + reportError("read pattern is not a read channel", channel.asExpr) + + def handleWrite(bind: Bind, valName: String, channel:Term, tp:TypeRepr): SelectorCaseExpr[F,S,R] = + val writeFun = makeLambda(valName,tp, bind.symbol, caseDef.rhs) + val e = caseDefGuard.getOrElse(valName, + reportError(s"not found binding ${valName} in write condition", channel.asExpr) + ) + if (channel.tpe <:< TypeRepr.of[WriteChannel[F,?]]) then + tp.asType match + case '[a] => + WriteExpression(channel.asExprOf[WriteChannel[F,a]],e.asExprOf[a], writeFun.asExprOf[a=>S]) + case _ => + reportError("Can't determinate type of write", caseDef.pattern.asExpr) + else + reportError("Write channel expected", channel.asExpr) + + def extractType[F[_]:Type](name: "read"|"write", channelTerm: Term, pat: Tree): TypeRepr = + import quotes.reflect._ + pat match + case Typed(_,tp) => tp.tpe + case _ => + TypeSelect(channelTerm,name).tpe + + def handleUnapply(chObj: Term, nameReadOrWrite: String, bind: Bind, valName: String, ePat: Tree, ch: String): SelectorCaseExpr[F,S,R] = + import quotes.reflect._ + if (chObj.tpe == '{gopher.Channel}.asTerm.tpe) + val chExpr = caseDefGuard.getOrElse(ch,reportError(s"select condition for ${ch} is not found",caseDef.pattern.asExpr)) + nameReadOrWrite match + case "Read" => + val elementType = extractType("read",chExpr, ePat) + handleRead(bind,valName,chExpr,elementType) + case "Write" => + val elementType = extractType("write",chExpr, ePat) + handleWrite(bind,valName,chExpr,elementType) + case _ => + reportError(s"Read or Write expected, we have ${nameReadOrWrite}", caseDef.pattern.asExpr) + else + reportError("Incorrect select pattern, expected or x:channel.{read,write} or Channel.{Read,Write}",chObj.asExpr) + + + + caseDef.pattern match + case Inlined(_,List(),body) => + parseCaseDef(CaseDef(body, caseDef.guard, caseDef.rhs)) + case b@Bind(v, tp@Typed(expr, TypeSelect(ch,"read"))) => + handleRead(b,v,ch,tp.tpe) + case b@Bind(v, tp@Typed(expr, Annotated(TypeSelect(ch,"read"),_))) => + handleRead(b,v,ch,tp.tpe) + case tp@Typed(expr, TypeSelect(ch,"read")) => + // todo: introduce 'dummy' val + reportError("binding var in read expression is mandatory", caseDef.pattern.asExpr) + case b@Bind(v, tp@Typed(expr, TypeSelect(ch,"write"))) => + handleWrite(b,v,ch,tp.tpe) + case b@Bind(v, tp@Typed(expr, Annotated(TypeSelect(ch,"write"),_))) => + handleWrite(b,v,ch,tp.tpe) + case b@Bind(v, tp@Typed(expr, TypeSelect(ch,"after"))) => + val timeoutFun = makeLambda(v, tp.tpe, b.symbol, caseDef.rhs) + val e = caseDefGuard.getOrElse(v, reportError(s"can't find condifion for $v",caseDef.pattern.asExpr)) + if (ch.tpe <:< TypeRepr.of[gopher.Time] || ch.tpe <:< TypeRepr.of[gopher.Time.type]) + TimeoutExpression(e.asExprOf[FiniteDuration], timeoutFun.asExprOf[FiniteDuration => S]) + else + reportError(s"Expected Time, we have ${ch.show}", ch.asExpr) + case b@Bind(v, tp@Typed(expr, TypeSelect(ch,"done"))) => + val readFun = makeLambda(v,tp.tpe,b.symbol,caseDef.rhs) + tp.tpe.asType match + case '[a] => + if (ch.tpe <:< TypeRepr.of[ReadChannel[F,a]]) then + DoneExression(ch.asExprOf[ReadChannel[F,a]],readFun.asExprOf[Unit=>S]) + else + reportError("done base is not a read channel", ch.asExpr) + case _ => + reportError("can't determinate read type", caseDef.pattern.asExpr) + case pat@Unapply(TypeApply(quotes.reflect.Select( + quotes.reflect.Select(chObj,nameReadOrWrite), + "unapply"),targs), + impl,List(b@Bind(e,ePat),Bind(ch,chPat))) => + handleUnapply(chObj, nameReadOrWrite, b, e, ePat, ch) + case pat@Typed(Unapply(TypeApply(quotes.reflect.Select( + quotes.reflect.Select(chobj,nameReadOrWrite), + "unapply"),targs), + impl,List(b@Bind(e,ePat),Bind(ch,chPat))),a) => + handleUnapply(chobj, nameReadOrWrite, b, e, ePat, ch) + case _ => + report.error( + s""" + expected one of: + v: channel.read + v: channel.write if v == expr + v: Time.after if v == expr + we have + ${caseDef.pattern.show} + (tree: ${caseDef.pattern}) + """, caseDef.pattern.asExpr) + reportError(s"unparsed caseDef pattern: ${caseDef.pattern}", caseDef.pattern.asExpr) + + end parseCaseDef + + + def parseCaseDefGuard(using Quotes)(caseDef: quotes.reflect.CaseDef): Map[String,quotes.reflect.Term] = + import quotes.reflect._ + caseDef.guard match + case Some(condition) => + parseSelectCondition(condition, Map.empty) + case None => + Map.empty + + + def parseSelectCondition(using Quotes)(condition: quotes.reflect.Term, + entries:Map[String,quotes.reflect.Term]): Map[String,quotes.reflect.Term] = + import quotes.reflect._ + condition match + case Apply(quotes.reflect.Select(Ident(v1),"=="),List(expr)) => + entries.updated(v1, expr) + case Apply(quotes.reflect.Select(frs, "&&" ), List(snd)) => + parseSelectCondition(snd, parseSelectCondition(frs, entries)) + case _ => + reportError( + s"""Invalid select guard form, expected one of + channelName == channelEpxr + writeBind == writeExpresion + condition && condition + we have + ${condition.show} + """, + condition.asExpr) + + + def makeLambda(using Quotes)(argName: String, + argType: quotes.reflect.TypeRepr, + oldArgSymbol: quotes.reflect.Symbol, + body: quotes.reflect.Term): quotes.reflect.Term = + import quotes.reflect._ + val widenReturnType = TransformUtil.veryWiden(body.tpe) + val mt = MethodType(List(argName))(_ => List(argType.widen), _ => widenReturnType) + Lambda(Symbol.spliceOwner, mt, (owner,args) => + substIdent(body,oldArgSymbol, args.head.asInstanceOf[Term], owner).changeOwner(owner)) + + + def substIdent(using Quotes)(term: quotes.reflect.Term, + fromSym: quotes.reflect.Symbol, + toTerm: quotes.reflect.Term, + owner: quotes.reflect.Symbol): quotes.reflect.Term = + import quotes.reflect._ + val argTransformer = new TreeMap() { + override def transformTerm(tree: Term)(owner: Symbol):Term = + tree match + case Ident(name) if tree.symbol == fromSym => toTerm + case _ => super.transformTerm(tree)(owner) + } + argTransformer.transformTerm(term)(owner) + + + def reportError(message: String, posExpr: Expr[?])(using Quotes): Nothing = + import quotes.reflect._ + report.error(message, posExpr) + throw new RuntimeException(s"Error in macro: $message") + + + diff --git a/shared/src/test/scala/gopher/channels/ForeverTerminationSuite.scala b/shared/src/test/scala/gopher/channels/ForeverTerminationSuite.scala index 0a5fa69a..5a9f64a5 100644 --- a/shared/src/test/scala/gopher/channels/ForeverTerminationSuite.scala +++ b/shared/src/test/scala/gopher/channels/ForeverTerminationSuite.scala @@ -19,11 +19,14 @@ class ForeverSuite extends FunSuite test("forevr not propagate signals after exit") { + implicit val printCode = cps.macros.flags.PrintCode val channel = makeChannel[Int](100) var sum = 0 - val f0 = select.aforever { - case x: channel.read => sum += x - throw ChannelClosedException() + val f0: Future[Unit] = select.aforever{ + case x: channel.read => { + sum += x + throw ChannelClosedException() + } } for {r2 <- channel.awrite(1) r3 <- channel.awrite(2) From 4e24cb098dd9843155733a4467c02828bf334e05 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Tue, 13 Jul 2021 19:37:24 +0300 Subject: [PATCH 097/161] scaladoc for Select.fold updated --- shared/src/main/scala/gopher/SelectFold.scala | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/shared/src/main/scala/gopher/SelectFold.scala b/shared/src/main/scala/gopher/SelectFold.scala index 7dd2c63e..2dee2981 100644 --- a/shared/src/main/scala/gopher/SelectFold.scala +++ b/shared/src/main/scala/gopher/SelectFold.scala @@ -1,6 +1,13 @@ package gopher +/** + * Helper namespace for Select.Fold return value + * @see [Select.fold] + **/ object SelectFold: + /** + * return value in Select.Fold which means that we should stop folding + **/ case class Done[S](s: S) \ No newline at end of file From cdb9e813e8cd27cad4fffd1b19a1b0f98b750d01 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Tue, 13 Jul 2021 19:46:10 +0300 Subject: [PATCH 098/161] 2.0.4 as last published version --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 56822878..970cf86d 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ For scala 3: - libraryDependencies += "com.github.rssh" %% "scala-gopher" % "2.0.3" + libraryDependencies += "com.github.rssh" %% "scala-gopher" % "2.0.4" For scala2: From 87413ff250fca2aa42146efa0515d4bcd2423692 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Tue, 13 Jul 2021 19:50:33 +0300 Subject: [PATCH 099/161] 2.0.5-SNAPSHOT --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index d9494fe9..1c2eecad 100644 --- a/build.sbt +++ b/build.sbt @@ -2,7 +2,7 @@ val dottyVersion = "3.0.1" //val dottyVersion = dottyLatestNightlyBuild.get -ThisBuild/version := "2.0.4" +ThisBuild/version := "2.0.5-SNAPSHOT" ThisBuild/versionScheme := Some("semver-spec") val sharedSettings = Seq( From d84605c2d8e0b470816a7b906ff89da39da6ff62 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Wed, 14 Jul 2021 07:52:26 +0300 Subject: [PATCH 100/161] more documentation --- shared/src/main/scala/gopher/Select.scala | 8 -------- .../src/main/scala/gopher/SelectForever.scala | 6 +----- .../src/main/scala/gopher/SelectGroup.scala | 20 ++++++++++++++----- .../src/main/scala/gopher/SelectMacro.scala | 6 +++--- 4 files changed, 19 insertions(+), 21 deletions(-) diff --git a/shared/src/main/scala/gopher/Select.scala b/shared/src/main/scala/gopher/Select.scala index f94531f7..221a85cd 100644 --- a/shared/src/main/scala/gopher/Select.scala +++ b/shared/src/main/scala/gopher/Select.scala @@ -107,14 +107,6 @@ class Select[F[_]](api: Gopher[F]): transparent inline def aforever(inline pf: PartialFunction[Any,Unit]): F[Unit] = ${ SelectMacro.aforeverImpl('pf, 'api) } - /* - transparent inline def aforever_async(inline pf: PartialFunction[Any,F[Unit]]): F[Unit] = - given CpsSchedulingMonad[F] = api.asyncMonad - async(using api.asyncMonad).apply { - val runner = new SelectForever[F](api) - runner.applyAsync(pf) - } - */ diff --git a/shared/src/main/scala/gopher/SelectForever.scala b/shared/src/main/scala/gopher/SelectForever.scala index bf26c1c1..8f1b2352 100644 --- a/shared/src/main/scala/gopher/SelectForever.scala +++ b/shared/src/main/scala/gopher/SelectForever.scala @@ -7,7 +7,7 @@ import scala.concurrent.duration._ /** - * forever Apply + * Result of `select.forever`: apply method accept partial pseudofunction which evalueated forever. **/ class SelectForever[F[_]](api: Gopher[F]) extends SelectGroupBuilder[F,Unit, Unit](api): @@ -17,10 +17,6 @@ class SelectForever[F[_]](api: Gopher[F]) extends SelectGroupBuilder[F,Unit, Uni SelectMacro.foreverImpl('pf,'api) } - transparent inline def applyAsync(inline pf: PartialFunction[Any,Unit]): F[Unit] = - ${ - SelectMacro.aforeverImpl('pf, 'api) - } def runAsync(): F[Unit] = given CpsSchedulingMonad[F] = api.asyncMonad diff --git a/shared/src/main/scala/gopher/SelectGroup.scala b/shared/src/main/scala/gopher/SelectGroup.scala index a1cbaa98..0404c847 100644 --- a/shared/src/main/scala/gopher/SelectGroup.scala +++ b/shared/src/main/scala/gopher/SelectGroup.scala @@ -14,8 +14,18 @@ import java.util.logging.{Level => LogLevel} /** - * Select group is a virtual 'lock' object, where only - * ne fro rieader and writer can exists at the sae time. + * Select group is a virtual 'lock' object. + * Readers and writers are grouped into select groups. When + * event about avaiability to read or to write is arrived and + * no current event group members is running, than run of one of the members + * is triggered. + * I.e. only one from group can run. + * + * Note, that application develeper usually not work with `SelectGroup` directly, + * it is created internally by `select` pseudostatement. + * + *@see [gopher.Select] + *@see [gopher.select] **/ class SelectGroup[F[_], S](api: Gopher[F]) extends SelectListeners[F,S,S]: @@ -28,10 +38,10 @@ class SelectGroup[F[_], S](api: Gopher[F]) extends SelectListeners[F,S,S]: * 2 - expired **/ val waitState: AtomicInteger = new AtomicInteger(0) - var call: Try[S] => Unit = { _ => () } + private var call: Try[S] => Unit = { _ => () } private inline def m = api.asyncMonad - val retval = m.adoptCallbackStyle[S](f => call=f) - val startTime = new AtomicLong(0L) + private val retval = m.adoptCallbackStyle[S](f => call=f) + private val startTime = new AtomicLong(0L) var timeoutScheduled: Option[Time.Scheduled] = None override def asyncMonad = api.asyncMonad diff --git a/shared/src/main/scala/gopher/SelectMacro.scala b/shared/src/main/scala/gopher/SelectMacro.scala index 5bcd5863..9fd6b10b 100644 --- a/shared/src/main/scala/gopher/SelectMacro.scala +++ b/shared/src/main/scala/gopher/SelectMacro.scala @@ -38,7 +38,7 @@ object SelectMacro: '{ $base.onRead($ch.done)($f) } - def selectListenerBuilder1[F[_]:Type, S:Type, R:Type, L <: SelectListeners[F,S,R]:Type]( + def selectListenerBuilder[F[_]:Type, S:Type, R:Type, L <: SelectListeners[F,S,R]:Type]( constructor: Expr[L], caseDefs: List[SelectorCaseExpr[F,S,R]])(using Quotes): Expr[L] = val s0 = constructor @@ -51,7 +51,7 @@ object SelectMacro: constructor: Expr[L], caseDefs: List[SelectorCaseExpr[F,S,R]], api:Expr[Gopher[F]])(using Quotes): Expr[R] = - val g = selectListenerBuilder1(constructor, caseDefs) + val g = selectListenerBuilder(constructor, caseDefs) // dotty bug if g.run val r = '{ await($g.runAsync())(using ${api}.asyncMonad) } r.asExprOf[R] @@ -60,7 +60,7 @@ object SelectMacro: constructor: Expr[L], caseDefs: List[SelectorCaseExpr[F,S,R]], api:Expr[Gopher[F]])(using Quotes): Expr[F[R]] = - val g = selectListenerBuilder1(constructor, caseDefs) + val g = selectListenerBuilder(constructor, caseDefs) // dotty bug if g.run val r = '{ $g.runAsync() } r.asExprOf[F[R]] From 7499cba685004cd8a19c0e963a6b3cf7f2bcd216 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Sun, 25 Jul 2021 10:39:43 +0300 Subject: [PATCH 101/161] adopted to dotty-cps-async 0.9.1 (async fir future starts with spawn) --- build.sbt | 4 +- .../scala/gopher/impl/PromiseChannel.scala | 27 ++++++++------ .../src/main/scala/gopher/ReadChannel.scala | 26 +++++++++++++ .../gopher/monads/ReadChannelCpsMonad.scala | 2 +- .../gopher/channels/MacroSelectSuite.scala | 1 + .../src/test/scala/gopher/monads/Queens.scala | 37 +++++++++++-------- 6 files changed, 67 insertions(+), 30 deletions(-) diff --git a/build.sbt b/build.sbt index 1c2eecad..2a704cbe 100644 --- a/build.sbt +++ b/build.sbt @@ -2,7 +2,7 @@ val dottyVersion = "3.0.1" //val dottyVersion = dottyLatestNightlyBuild.get -ThisBuild/version := "2.0.5-SNAPSHOT" +ThisBuild/version := "2.0.5" ThisBuild/versionScheme := Some("semver-spec") val sharedSettings = Seq( @@ -10,7 +10,7 @@ val sharedSettings = Seq( scalaVersion := dottyVersion, name := "scala-gopher", resolvers += "Local Ivy Repository" at "file://"+Path.userHome.absolutePath+"/.ivy2/local", - libraryDependencies += "com.github.rssh" %%% "dotty-cps-async" % "0.9.0", + libraryDependencies += "com.github.rssh" %%% "dotty-cps-async" % "0.9.1", libraryDependencies += "org.scalameta" %%% "munit" % "0.7.26" % Test, testFrameworks += new TestFramework("munit.Framework") ) diff --git a/jvm/src/main/scala/gopher/impl/PromiseChannel.scala b/jvm/src/main/scala/gopher/impl/PromiseChannel.scala index bb048e99..4f2783ba 100644 --- a/jvm/src/main/scala/gopher/impl/PromiseChannel.scala +++ b/jvm/src/main/scala/gopher/impl/PromiseChannel.scala @@ -34,8 +34,10 @@ import scala.util.Failure case Some((a,f)) => val ar: AnyRef = a.asInstanceOf[AnyRef] // if (ref.compareAndSet(null,ar) && !closed.get() ) then - closed.lazySet(true) - taskExecutor.execute(()=> f(Success(()))) + closed.set(true) + taskExecutor.execute{ ()=> + f(Success(())) + } writer.markUsed() step() else @@ -48,21 +50,24 @@ import scala.util.Failure def addDoneReader(reader: Reader[Unit]): Unit = - if (!closed.get()) then + if (!closed.get() || !readed.get) then doneReaders.add(reader) + if (closed.get()) then + step() else var done = false while(!done & !reader.isExpired) { - reader.capture() match - case Some(f) => - reader.markUsed() - taskExecutor.execute(()=>f(Success(()))) - done = true - case None => - if (!reader.isExpired) - Thread.onSpinWait() + reader.capture() match + case Some(f) => + reader.markUsed() + taskExecutor.execute(()=>f(Success(()))) + done = true + case None => + if (!reader.isExpired) + Thread.onSpinWait() } + def close(): Unit = diff --git a/shared/src/main/scala/gopher/ReadChannel.scala b/shared/src/main/scala/gopher/ReadChannel.scala index d1b8b5a6..b00af8b9 100644 --- a/shared/src/main/scala/gopher/ReadChannel.scala +++ b/shared/src/main/scala/gopher/ReadChannel.scala @@ -214,6 +214,10 @@ object ReadChannel: retval.close() retval + /** + *@param c - iteratable to read from. + *@return channel, which will emit all elements from 'c' and then close. + **/ def fromIterable[F[_],A](c: IterableOnce[A])(using Gopher[F]): ReadChannel[F,A] = given asyncMonad: CpsSchedulingMonad[F] = summon[Gopher[F]].asyncMonad val retval = makeChannel[A]() @@ -227,6 +231,28 @@ object ReadChannel: }) retval + /** + *@return one copy of `a` and close. + **/ + def once[F[_],A](a: A)(using Gopher[F]): ReadChannel[F,A] = + fromIterable(List(a)) + + /** + *@param a - value to produce + *@return channel which emit value of a in loop and never close + **/ + def always[F[_],A](a: A)(using Gopher[F]): ReadChannel[F,A] = + given asyncMonad: CpsSchedulingMonad[F] = summon[Gopher[F]].asyncMonad + val retval = makeChannel[A]() + summon[Gopher[F]].spawnAndLogFail( + async{ + while(true) { + retval.write(a) + } + } + ) + retval + def fromFuture[F[_],A](f: F[A])(using Gopher[F]): ReadChannel[F,A] = diff --git a/shared/src/main/scala/gopher/monads/ReadChannelCpsMonad.scala b/shared/src/main/scala/gopher/monads/ReadChannelCpsMonad.scala index 98c91f6a..f1a35596 100644 --- a/shared/src/main/scala/gopher/monads/ReadChannelCpsMonad.scala +++ b/shared/src/main/scala/gopher/monads/ReadChannelCpsMonad.scala @@ -23,6 +23,6 @@ given futureToReadChannel[F[_]](using Gopher[F]): CpsMonadConversion[F, [A] =>> def apply[T](ft: F[T]): ReadChannel[F,T] = futureInput(ft) - + diff --git a/shared/src/test/scala/gopher/channels/MacroSelectSuite.scala b/shared/src/test/scala/gopher/channels/MacroSelectSuite.scala index 4b872d8a..82a40bac 100644 --- a/shared/src/test/scala/gopher/channels/MacroSelectSuite.scala +++ b/shared/src/test/scala/gopher/channels/MacroSelectSuite.scala @@ -419,6 +419,7 @@ class MacroSelectSuite extends FunSuite } val f1 = ch.awrite(1) async { + await(f1) val r = await(sf) assert(r==1) } diff --git a/shared/src/test/scala/gopher/monads/Queens.scala b/shared/src/test/scala/gopher/monads/Queens.scala index d7d53519..917b444d 100644 --- a/shared/src/test/scala/gopher/monads/Queens.scala +++ b/shared/src/test/scala/gopher/monads/Queens.scala @@ -22,7 +22,25 @@ class QueensSuite extends FunSuite { busyLRDiagonals:Set[Int], busyRLDiagonals:Set[Int], queens: Vector[(Int,Int)] - ); + ) { + + def isBusy(i:Int, j:Int): Boolean = + busyRows.contains(i) || + busyColumns.contains(j) || + busyLRDiagonals.contains(i-j) || + busyRLDiagonals.contains(i+j) + + + def put(i:Int, j:Int): State = + copy( busyRows = busyRows + i, + busyColumns = busyColumns + j, + busyLRDiagonals = busyLRDiagonals + (i-j), + busyRLDiagonals = busyRLDiagonals + (i+j), + queens = queens :+ (i,j) + ) + + + } val N = 8 @@ -31,19 +49,8 @@ class QueensSuite extends FunSuite { async[Future] { val i = state.queens.length if i < N then - for{ - j <- 0 until N if !state.busyColumns.contains(j) && - !state.busyLRDiagonals.contains(i-j) && - !state.busyRLDiagonals.contains(i+j) - } { - val newPos = (i,j) - val nState = state.copy( busyRows = state.busyRows + i, - busyColumns = state.busyColumns + j, - busyLRDiagonals = state.busyLRDiagonals + (i-j), - busyRLDiagonals = state.busyRLDiagonals + (i+j), - queens = state.queens :+ newPos ) - ch.write(nState) - } + for{ j <- 0 until N if !state.isBusy(i,j) } + ch.write(state.put(i,j)) ch.close() } ch @@ -51,9 +58,7 @@ class QueensSuite extends FunSuite { def solutions(state: State): ReadChannel[Future,State] = async[[X] =>> ReadChannel[Future,X]] { if(state.queens.size < N) then - //println("state:"+state.queens) val nextState = await(putQueen(state)) - //println("next-state:"+state.queens) await(solutions(nextState)) else state From b0596c800af60c5d808a465e9a929bba6d9d1ef9 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Thu, 5 Aug 2021 19:58:38 +0300 Subject: [PATCH 102/161] dotty-cps-async-0.9.2, scalajs-1.7.0 --- README.md | 2 +- build.sbt | 7 +++---- project/plugins.sbt | 2 +- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 970cf86d..f9d9ed50 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ For scala 3: - libraryDependencies += "com.github.rssh" %% "scala-gopher" % "2.0.4" + libraryDependencies += "com.github.rssh" %% "scala-gopher" % "2.0.5" For scala2: diff --git a/build.sbt b/build.sbt index 2a704cbe..47579481 100644 --- a/build.sbt +++ b/build.sbt @@ -2,7 +2,7 @@ val dottyVersion = "3.0.1" //val dottyVersion = dottyLatestNightlyBuild.get -ThisBuild/version := "2.0.5" +ThisBuild/version := "2.0.6" ThisBuild/versionScheme := Some("semver-spec") val sharedSettings = Seq( @@ -10,9 +10,8 @@ val sharedSettings = Seq( scalaVersion := dottyVersion, name := "scala-gopher", resolvers += "Local Ivy Repository" at "file://"+Path.userHome.absolutePath+"/.ivy2/local", - libraryDependencies += "com.github.rssh" %%% "dotty-cps-async" % "0.9.1", - libraryDependencies += "org.scalameta" %%% "munit" % "0.7.26" % Test, - testFrameworks += new TestFramework("munit.Framework") + libraryDependencies += "com.github.rssh" %%% "dotty-cps-async" % "0.9.2", + libraryDependencies += "org.scalameta" %%% "munit" % "0.7.27" % Test, ) lazy val root = project diff --git a/project/plugins.sbt b/project/plugins.sbt index 9ed7f66a..a5c778e6 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -2,5 +2,5 @@ addSbtPlugin("com.jsuereth" % "sbt-pgp" % "2.0.2") addSbtPlugin("com.typesafe.sbt" % "sbt-site" % "1.4.1") addSbtPlugin("com.typesafe.sbt" % "sbt-ghpages" % "0.6.3") addSbtPlugin("org.portable-scala" % "sbt-scalajs-crossproject" % "1.0.0") -addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.5.1") +addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.7.0") From 2edd251ef87df6c5f61372f22200b51c63bb910f Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Thu, 5 Aug 2021 20:50:41 +0300 Subject: [PATCH 103/161] 2.0.6 in readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f9d9ed50..2ad690a6 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ For scala 3: - libraryDependencies += "com.github.rssh" %% "scala-gopher" % "2.0.5" + libraryDependencies += "com.github.rssh" %% "scala-gopher" % "2.0.6" For scala2: From 7e28e3248366ffeb7db6edac0eb24ca53f962b5a Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Thu, 5 Aug 2021 20:51:22 +0300 Subject: [PATCH 104/161] 2.0.7-SNAPSHOT --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index 47579481..95d1fd88 100644 --- a/build.sbt +++ b/build.sbt @@ -2,7 +2,7 @@ val dottyVersion = "3.0.1" //val dottyVersion = dottyLatestNightlyBuild.get -ThisBuild/version := "2.0.6" +ThisBuild/version := "2.0.7-SNAPSHOT" ThisBuild/versionScheme := Some("semver-spec") val sharedSettings = Seq( From f3186fb51d7448265043cab0eb83303be21866f8 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Sun, 15 Aug 2021 11:18:07 +0300 Subject: [PATCH 105/161] fixed possible lost of event in situation many readers, few writers in unbuffered streams. --- .../main/scala/gopher/impl/GuardedSPSCBufferedChannel.scala | 1 - .../main/scala/gopher/impl/GuardedSPSCUnbufferedChannel.scala | 4 ++++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/jvm/src/main/scala/gopher/impl/GuardedSPSCBufferedChannel.scala b/jvm/src/main/scala/gopher/impl/GuardedSPSCBufferedChannel.scala index 641de118..e9bfa209 100644 --- a/jvm/src/main/scala/gopher/impl/GuardedSPSCBufferedChannel.scala +++ b/jvm/src/main/scala/gopher/impl/GuardedSPSCBufferedChannel.scala @@ -81,7 +81,6 @@ taskExecutor: ExecutorService) extends GuardedSPSCBaseChannel[F,A](gopherApi,con while(progress) { progress = false if !state.isEmpty() then - val a = state.startRead() progress |= processReadsStep() else if isClosed then diff --git a/jvm/src/main/scala/gopher/impl/GuardedSPSCUnbufferedChannel.scala b/jvm/src/main/scala/gopher/impl/GuardedSPSCUnbufferedChannel.scala index a439989c..56998859 100644 --- a/jvm/src/main/scala/gopher/impl/GuardedSPSCUnbufferedChannel.scala +++ b/jvm/src/main/scala/gopher/impl/GuardedSPSCUnbufferedChannel.scala @@ -54,6 +54,10 @@ class GuardedSPSCUnbufferedChannel[F[_]:CpsAsyncMonad,A]( progress = true progressWaitReader(reader) } + if !writersLoopDone then + // we have reader, should return one back if we want to start again + // TODO: write test for this case + readers.addFirst(reader) } if (isClosed && (readers.isEmpty || writers.isEmpty) ) then progress |= processWriteClose() From 4c87d52989e5232da51debca4fe0ee5abf164265 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Fri, 3 Sep 2021 07:18:54 +0300 Subject: [PATCH 106/161] refactored Expirable.caputure to return 3-variant value; added support of cps generators --- build.sbt | 10 +- js/src/main/scala/gopher/JSGopher.scala | 11 ++- .../main/scala/gopher/impl/BaseChannel.scala | 22 +++-- .../scala/gopher/impl/BufferedChannel.scala | 10 +- .../scala/gopher/impl/PromiseChannel.scala | 19 ++-- .../scala/gopher/impl/UnbufferedChannel.scala | 12 ++- js/src/test/scala/gopher/util/Debug.scala | 41 ++++++++ jvm/src/main/scala/gopher/JVMGopher.scala | 5 +- .../gopher/impl/GuardedSPSCBaseChannel.scala | 88 ++++++++++++----- .../impl/GuardedSPSCBufferedChannel.scala | 31 +++--- .../impl/GuardedSPSCUnbufferedChannel.scala | 29 +++--- .../scala/gopher/impl/PromiseChannel.scala | 54 +++++------ .../DuppedChannelsMultipleSuite.scala | 46 +++------ .../stream/JVMBasicGeneratorSuite.scala | 76 +++++++++++++++ jvm/src/test/scala/gopher/util/Debug.scala | 47 +++++++++ .../scala/gopher/ChannelClosedException.scala | 4 +- shared/src/main/scala/gopher/Gopher.scala | 9 ++ .../src/main/scala/gopher/ReadChannel.scala | 21 +++- .../src/main/scala/gopher/SelectGroup.scala | 66 +++++++++---- .../scala/gopher/impl/AppendReadChannel.scala | 2 +- .../main/scala/gopher/impl/DuppedInput.scala | 26 ++--- .../main/scala/gopher/impl/Expirable.scala | 23 ++++- .../gopher/impl/FilteredReadChannel.scala | 4 +- .../scala/gopher/impl/MappedReadChannel.scala | 4 +- .../scala/gopher/impl/OrReadChannel.scala | 15 +-- .../src/main/scala/gopher/impl/Writer.scala | 2 +- .../gopher/impl/WriterWithExpireTime.scala | 21 ++-- .../gopher/channels/DuppedChannelsSuite.scala | 2 +- .../gopher/stream/BasicGeneratorSuite.scala | 95 +++++++++++++++++++ 29 files changed, 598 insertions(+), 197 deletions(-) create mode 100644 js/src/test/scala/gopher/util/Debug.scala create mode 100644 jvm/src/test/scala/gopher/stream/JVMBasicGeneratorSuite.scala create mode 100644 jvm/src/test/scala/gopher/util/Debug.scala create mode 100644 shared/src/test/scala/gopher/stream/BasicGeneratorSuite.scala diff --git a/build.sbt b/build.sbt index 95d1fd88..3bc2f492 100644 --- a/build.sbt +++ b/build.sbt @@ -10,7 +10,7 @@ val sharedSettings = Seq( scalaVersion := dottyVersion, name := "scala-gopher", resolvers += "Local Ivy Repository" at "file://"+Path.userHome.absolutePath+"/.ivy2/local", - libraryDependencies += "com.github.rssh" %%% "dotty-cps-async" % "0.9.2", + libraryDependencies += "com.github.rssh" %%% "dotty-cps-async" % "0.9.3-SNAPSHOT", libraryDependencies += "org.scalameta" %%% "munit" % "0.7.27" % Test, ) @@ -34,6 +34,14 @@ lazy val gopher = crossProject(JSPlatform, JVMPlatform) .disablePlugins(SitePreviewPlugin) .jvmSettings( scalacOptions ++= Seq( "-unchecked", "-Ycheck:macros", "-uniqid", "-Xprint:types" ), + fork := true, + /* + javaOptions ++= Seq( + "--add-opens", + "java.base/java.lang=ALL-UNNAMED", + s"-javaagent:${System.getProperty("user.home")}/.ivy2/local/com.github.rssh/trackedfuture_3/0.5.0/jars/trackedfuture_3-assembly.jar" + ) + */ ).jsSettings( libraryDependencies += ("org.scala-js" %%% "scalajs-java-logging" % "1.0.0").cross(CrossVersion.for3Use2_13), // TODO: switch to ModuleES ? diff --git a/js/src/main/scala/gopher/JSGopher.scala b/js/src/main/scala/gopher/JSGopher.scala index 5cc73cff..e0db6454 100644 --- a/js/src/main/scala/gopher/JSGopher.scala +++ b/js/src/main/scala/gopher/JSGopher.scala @@ -1,9 +1,11 @@ package gopher -import cps._ +import cps.* import java.util.Timer -import java.util.logging._ -import scala.concurrent.duration._ +import java.util.logging.* +import scala.concurrent.ExecutionContext +import scala.concurrent.duration.* +import scala.scalajs.concurrent.* class JSGopher[F[_]:CpsSchedulingMonad](cfg: JSGopherConfig) extends Gopher[F]: @@ -28,6 +30,8 @@ class JSGopher[F[_]:CpsSchedulingMonad](cfg: JSGopherConfig) extends Gopher[F]: def log(level: Level, message: String, ex: Throwable| Null): Unit = currentLogFun.apply(level,message,ex) + def taskExecutionContext: ExecutionContext = JSExecutionContext.queue + private var currentLogFun: (Level, String, Throwable|Null )=> Unit = { (level,message,ex) => System.err.println(s"${level}:${message}"); if !(ex eq null) then @@ -46,5 +50,6 @@ object JSGopher extends GopherAPI: val timer = new Timer("gopher") + val Gopher = JSGopher diff --git a/js/src/main/scala/gopher/impl/BaseChannel.scala b/js/src/main/scala/gopher/impl/BaseChannel.scala index e40c2250..6f96d6ff 100644 --- a/js/src/main/scala/gopher/impl/BaseChannel.scala +++ b/js/src/main/scala/gopher/impl/BaseChannel.scala @@ -42,12 +42,13 @@ abstract class BaseChannel[F[_],A](override val gopherApi: JSGopher[F]) extends def addWriter(writer: Writer[A]): Unit = if (closed) { - writer.capture().foreach{ (a,f) => - writer.markUsed() - submitTask( () => - f(Failure(new ChannelClosedException())) - ) - } + writer.capture() match + case Expirable.Capture.Ready((a,f)) => + writer.markUsed() + submitTask( () => + f(Failure(new ChannelClosedException())) + ) + case _ => } else { writers.enqueue(writer) process() @@ -56,13 +57,14 @@ abstract class BaseChannel[F[_],A](override val gopherApi: JSGopher[F]) extends def addDoneReader(reader: Reader[Unit]): Unit = if (closed && isEmpty) { reader.capture() match - case Some(f) => + case Expirable.Capture.Ready(f) => reader.markUsed() submitTask( () => f(Success(()))) - case None => + case Expirable.Capture.WaitChangeComplete => // mb is blocked and will be evaluated in doneReaders.enqueue(reader) process() + case Expirable.Capture.Expired => } else { doneReaders.enqueue(reader) process() @@ -79,10 +81,10 @@ abstract class BaseChannel[F[_],A](override val gopherApi: JSGopher[F]) extends val v = queue.dequeue() if (!v.isExpired) then v.capture() match - case Some(a) => + case Expirable.Capture.Ready(a) => v.markUsed() action(a) - case None => + case _ => // do nothing. // exists case, when this is possible: wheb we close channel from // select-group callback, which is evaluated now. diff --git a/js/src/main/scala/gopher/impl/BufferedChannel.scala b/js/src/main/scala/gopher/impl/BufferedChannel.scala index cf545ffb..497c34b8 100644 --- a/js/src/main/scala/gopher/impl/BufferedChannel.scala +++ b/js/src/main/scala/gopher/impl/BufferedChannel.scala @@ -71,15 +71,15 @@ class BufferedChannel[F[_]:CpsAsyncMonad, A](gopherApi: JSGopher[F], bufSize: In if (!writers.isEmpty && !isFull) then val writer = writers.dequeue() writer.capture() match - case Some((a,f)) => + case Expirable.Capture.Ready((a,f)) => internalEnqueue(a) writer.markUsed() submitTask( () => f(Success(())) ) progress = true - case None => - if (!writer.isExpired) then - // impossible, we have no parallel execution - throw DeadlockDetected() + case Expirable.Capture.WaitChangeComplete => + // impossible, we have no parallel execution + throw DeadlockDetected() + case Expirable.Capture.Expired => progress diff --git a/js/src/main/scala/gopher/impl/PromiseChannel.scala b/js/src/main/scala/gopher/impl/PromiseChannel.scala index 08b0721d..83bd539d 100644 --- a/js/src/main/scala/gopher/impl/PromiseChannel.scala +++ b/js/src/main/scala/gopher/impl/PromiseChannel.scala @@ -21,31 +21,30 @@ class PromiseChannel[F[_]:CpsAsyncMonad, A](gopherApi: JSGopher[F]) extends Base // we have only one writer. while (!writers.isEmpty && value.isEmpty) { val w = writers.dequeue() - if (!w.isExpired) then - w.capture() match - case Some((a,f)) => + w.capture() match + case Expirable.Capture.Ready((a,f)) => w.markUsed() submitTask(()=>f(Success(()))) value = Some(a) closed = true // we can't havw more than one unexpired - case None => - if (!w.isExpired) then + case Expirable.Capture.WaitChangeComplete => // impossible in js, + // (mb processNextTick()?) throw new DeadlockDetected() + case Expirable.Capture.Expired => } if (!readers.isEmpty && value.isDefined) { while(!readers.isEmpty && !readed) { val r = readers.dequeue() - if (!r.isExpired) then - r.capture() match - case Some(f) => + r.capture() match + case Expirable.Capture.Ready(f) => r.markUsed() submitTask(()=>f(Success(value.get))) readed = true - case None => - if (!r.isExpired) + case Expirable.Capture.WaitChangeComplete => throw new DeadlockDetected() + case Expirable.Capture.Expired => } //if (readed) { // processCloseDone() diff --git a/js/src/main/scala/gopher/impl/UnbufferedChannel.scala b/js/src/main/scala/gopher/impl/UnbufferedChannel.scala index 9656aaec..a73dc134 100644 --- a/js/src/main/scala/gopher/impl/UnbufferedChannel.scala +++ b/js/src/main/scala/gopher/impl/UnbufferedChannel.scala @@ -25,21 +25,25 @@ class UnbufferedChannel[F[_]:CpsAsyncMonad, A](gopherApi: JSGopher[F]) extends B findWriter() match case Some(writer) => reader.capture() match - case Some(readFun) => + case Expirable.Capture.Ready(readFun) => writer.capture() match - case Some((a,writeFun)) => + case Expirable.Capture.Ready((a,writeFun)) => submitTask( () => readFun(Success(a))) submitTask( () => writeFun(Success(())) ) progress = true done = true writer.markUsed() reader.markUsed() - case None => + case _ => // impossible, because in js we have-no interleavinf, bug anyway // let's fallback reader.markFree() readers.prepend(reader) - case None => + case Expirable.Capture.WaitChangeComplete => + // impossible, but let's fallback + // TODO: prepend reader and skip event + writers.prepend(writer) + case Expirable.Capture.Expired => // impossible, but let's fallback writers.prepend(writer) case None => diff --git a/js/src/test/scala/gopher/util/Debug.scala b/js/src/test/scala/gopher/util/Debug.scala new file mode 100644 index 00000000..9569d0dc --- /dev/null +++ b/js/src/test/scala/gopher/util/Debug.scala @@ -0,0 +1,41 @@ +package gopher.util + +import java.util.logging.{Level => LogLevel} + +object Debug { + + type InMemoryLog = java.util.concurrent.ConcurrentLinkedQueue[(Long, String, Throwable)] + + def inMemoryLogFun(inMemoryLog: InMemoryLog): (LogLevel, String, Throwable|Null) => Unit = + (level,msg, ex) => inMemoryLog.add((Thread.currentThread().getId(), msg,ex)) + + def showInMemoryLog(inMemoryLog: InMemoryLog): Unit = { + while(!inMemoryLog.isEmpty) { + val r = inMemoryLog.poll() + if (r != null) { + println(r) + } + } + } + + + def showTraces(maxTracesToShow: Int): Unit = { + val traces = Thread.getAllStackTraces(); + val it = traces.entrySet().iterator() + while(it.hasNext()) { + val e = it.next(); + println(e.getKey()); + val elements = e.getValue() + var sti = 0 + var wasPark = false + while(sti < elements.length && sti < maxTracesToShow && !wasPark) { + val st = elements(sti) + println(" "*10 + st) + sti = sti + 1; + wasPark = (st.getMethodName == "park") + } + } + } + + +} \ No newline at end of file diff --git a/jvm/src/main/scala/gopher/JVMGopher.scala b/jvm/src/main/scala/gopher/JVMGopher.scala index e6b0c7a0..eac8bab6 100644 --- a/jvm/src/main/scala/gopher/JVMGopher.scala +++ b/jvm/src/main/scala/gopher/JVMGopher.scala @@ -9,6 +9,7 @@ import java.util.concurrent.ForkJoinPool import java.util.concurrent.atomic.AtomicReference import java.util.Timer import java.util.logging._ +import scala.concurrent.ExecutionContext import scala.concurrent.duration._ @@ -18,7 +19,7 @@ class JVMGopher[F[_]:CpsSchedulingMonad](cfg: JVMGopherConfig) extends Gopher[F] def makeChannel[A](bufSize:Int = 0, autoClose: Boolean = false) = if autoClose then - PromiseChannel[F,A](this, taskExecutor) + PromiseChannel[F,A](this, cfg.taskExecutor) else if (bufSize == 0) GuardedSPSCUnbufferedChannel[F,A](this, cfg.controlExecutor,cfg.taskExecutor) @@ -35,7 +36,7 @@ class JVMGopher[F[_]:CpsSchedulingMonad](cfg: JVMGopherConfig) extends Gopher[F] def log(level: Level, message: String, ex: Throwable| Null): Unit = currentLogFun.get().apply(level,message,ex) - def taskExecutor = cfg.taskExecutor + lazy val taskExecutionContext = ExecutionContext.fromExecutor(cfg.taskExecutor) def scheduledExecutor = JVMGopher.scheduledExecutor diff --git a/jvm/src/main/scala/gopher/impl/GuardedSPSCBaseChannel.scala b/jvm/src/main/scala/gopher/impl/GuardedSPSCBaseChannel.scala index 9265d6b8..dc6ed404 100644 --- a/jvm/src/main/scala/gopher/impl/GuardedSPSCBaseChannel.scala +++ b/jvm/src/main/scala/gopher/impl/GuardedSPSCBaseChannel.scala @@ -38,22 +38,30 @@ abstract class GuardedSPSCBaseChannel[F[_]:CpsAsyncMonad,A](override val gopherA def addReader(reader: Reader[A]): Unit = if (reader.canExpire) then - readers.removeIf( _.isExpired ) + readers.removeIf( _.isExpired ) + // if (publishedClosed.get()) then + // tryClosedRead() + // else readers.add(reader) controlExecutor.submit(stepRunnable) def addWriter(writer: Writer[A]): Unit = if (writer.canExpire) then - writers.removeIf( _.isExpired ) + writers.removeIf( _.isExpired ) if (publishedClosed.get()) then - closeWriter(writer) - else - writers.add(writer) - controlExecutor.submit(stepRunnable) + closeWriter(writer) + else + writers.add(writer) + controlExecutor.submit(stepRunnable) def addDoneReader(reader: Reader[Unit]): Unit = - doneReaders.add(reader) - controlExecutor.submit(stepRunnable) + if (reader.canExpire) + doneReaders.removeIf( _.isExpired ) + if (publishedClosed.get()) then + closeDoneReader(reader) + else + doneReaders.add(reader) + controlExecutor.submit(stepRunnable) def close(): Unit = publishedClosed.set(true) @@ -96,38 +104,55 @@ abstract class GuardedSPSCBaseChannel[F[_]:CpsAsyncMonad,A](override val gopherA // impossible, let'a r false - + // precondition: writers are empty protected def processReadClose(): Boolean = + require(writers.isEmpty) var progress = false while(!readers.isEmpty) { val r = readers.poll() if (!(r eq null) && !r.isExpired) then r.capture() match - case Some(f) => + case Expirable.Capture.Ready(f) => progress = true - taskExecutor.execute(() => f(Failure(new ChannelClosedException())) ) + //println("sending signal in processReadClose"); + //val prevEx = new RuntimeException("prev") + taskExecutor.execute(() => { + //try + //println(s"calling $f, channel = ${GuardedSPSCBaseChannel.this}") + // prevEx.printStackTrace() + val debugInfo = s"channel=${this}, writersEmpty=${writers.isEmpty}, readersEmpty=${readers.isEmpty}, r=$r, f=$f" + f(Failure(new ChannelClosedException(debugInfo))) + //catch + // case ex: Exception => + // println(s"exception in close-reader, channel=${GuardedSPSCBaseChannel.this}, f=$f, r=$r") + // throw ex + }) r.markUsed() - case None => - progress = true + case Expirable.Capture.WaitChangeComplete => progressWaitReader(r) + case Expirable.Capture.Expired => + progress = true } progress + // TODO: remove. If we have writers in queue, protected def processWriteClose(): Boolean = var progress = false while(!writers.isEmpty) { val w = writers.poll() if !(w eq null) && !w.isExpired then w.capture() match - case Some((a,f)) => + case Expirable.Capture.Ready((a,f)) => progress = true taskExecutor.execute(() => f(Failure(new ChannelClosedException)) ) w.markUsed() - case None => - progress = true + case Expirable.Capture.WaitChangeComplete => progressWaitWriter(w) + case Expirable.Capture.Expired => + progress = true } progress + protected def processDoneClose(): Boolean = { var progress = false @@ -135,28 +160,45 @@ abstract class GuardedSPSCBaseChannel[F[_]:CpsAsyncMonad,A](override val gopherA val r = doneReaders.poll() if !(r eq null) && !r.isExpired then r.capture() match - case Some(f) => + case Expirable.Capture.Ready(f) => progress = true taskExecutor.execute(() => f(Success(()))) - r.markUsed() - case None => + r.markUsed() + case Expirable.Capture.WaitChangeComplete => progressWaitDoneReader(r) + case Expirable.Capture.Expired => + progress = true } progress } + protected def closeDoneReader(r: Reader[Unit]): Unit = { + while + r.capture() match + case Expirable.Capture.Ready(f) => + taskExecutor.execute(()=>f(Success(()))) + r.markUsed() + false + case Expirable.Capture.WaitChangeComplete => + progressWaitDoneReader(r) + true + case Expirable.Capture.Expired => + false + do () + } protected def closeWriter(w: Writer[A]): Unit = { var done = false while (!done && !w.isExpired) w.capture() match - case Some((a,f)) => + case Expirable.Capture.Ready((a,f)) => taskExecutor.execute(() => f(Failure(new ChannelClosedException)) ) w.markUsed() done = true - case None => - if (!w.isExpired) then - Thread.onSpinWait() + case Expirable.Capture.WaitChangeComplete => + Thread.onSpinWait() + case Expirable.Capture.Expired => + done = true } diff --git a/jvm/src/main/scala/gopher/impl/GuardedSPSCBufferedChannel.scala b/jvm/src/main/scala/gopher/impl/GuardedSPSCBufferedChannel.scala index e9bfa209..96b2d176 100644 --- a/jvm/src/main/scala/gopher/impl/GuardedSPSCBufferedChannel.scala +++ b/jvm/src/main/scala/gopher/impl/GuardedSPSCBufferedChannel.scala @@ -85,11 +85,12 @@ taskExecutor: ExecutorService) extends GuardedSPSCBaseChannel[F,A](gopherApi,con else if isClosed then progress |= processDoneClose() - progress |= processReadClose() - if (!state.isFull() && !isClosed) then + if (writers.isEmpty) then + progress |= processReadClose() + if (!state.isFull()) then progress |= processWriteStep() - if (isClosed) - progress |= processWriteClose() + //if (isClosed) + // progress |= processWriteClose() if (!progress) { state.publish() if (! checkLeaveStep()) { @@ -111,16 +112,17 @@ taskExecutor: ExecutorService) extends GuardedSPSCBaseChannel[F,A](gopherApi,con val reader = readers.poll() if !(reader eq null) && !reader.isExpired then reader.capture() match - case Some(f) => + case Expirable.Capture.Ready(f) => // try/cath arround f is a reader reponsability taskExecutor.execute(() => f(Success(a))) reader.markUsed() state.finishRead() progress = true done = true - case None => - if !reader.isExpired then - nonExpiredBusyReads = nonExpiredBusyReads.enqueue(reader) + case Expirable.Capture.WaitChangeComplete => + nonExpiredBusyReads = nonExpiredBusyReads.enqueue(reader) + case Expirable.Capture.Expired => + progress = true } while(nonExpiredBusyReads.nonEmpty) { // not in this thread, but progress. @@ -140,10 +142,12 @@ taskExecutor: ExecutorService) extends GuardedSPSCBaseChannel[F,A](gopherApi,con val writer = writers.poll() if !(writer eq null ) && ! writer.isExpired then writer.capture() match - case Some((a,f)) => + case Expirable.Capture.Ready((a,f)) => done = true if (state.write(a)) then - taskExecutor.execute(() => f(Success(()))) + taskExecutor.execute( + () => f(Success(())) + ) progress = true writer.markUsed() else @@ -152,9 +156,10 @@ taskExecutor: ExecutorService) extends GuardedSPSCBaseChannel[F,A](gopherApi,con //log("impossibe,unsuccesfull write after !isFull") writer.markFree() writers.addFirst(writer) - case None => - if (!writer.isExpired) - nonExpiredBusyWriters = nonExpiredBusyWriters.enqueue(writer) + case Expirable.Capture.WaitChangeComplete => + nonExpiredBusyWriters = nonExpiredBusyWriters.enqueue(writer) + case Expirable.Capture.Expired => + progress = true } while(nonExpiredBusyWriters.nonEmpty) { progress = true diff --git a/jvm/src/main/scala/gopher/impl/GuardedSPSCUnbufferedChannel.scala b/jvm/src/main/scala/gopher/impl/GuardedSPSCUnbufferedChannel.scala index 56998859..8d0f4eeb 100644 --- a/jvm/src/main/scala/gopher/impl/GuardedSPSCUnbufferedChannel.scala +++ b/jvm/src/main/scala/gopher/impl/GuardedSPSCUnbufferedChannel.scala @@ -33,38 +33,39 @@ class GuardedSPSCUnbufferedChannel[F[_]:CpsAsyncMonad,A]( if (!(writer eq null) && !writer.isExpired) then // now we have reader and writer reader.capture() match - case Some(readFun) => + case Expirable.Capture.Ready(readFun) => + progress = true writer.capture() match - case Some((a,writeFun)) => + case Expirable.Capture.Ready((a,writeFun)) => // great, now we have all taskExecutor.execute(()=>readFun(Success(a))) taskExecutor.execute(()=>writeFun(Success(()))) - progress = true reader.markUsed() writer.markUsed() writersLoopDone = true - case None => - // reader same, other writer + case Expirable.Capture.WaitChangeComplete => reader.markFree() - progress = true // return when progressWaitWriter(writer) - case None => + case Expirable.Capture.Expired => + reader.markFree() + case Expirable.Capture.WaitChangeComplete => writers.addFirst(writer) writersLoopDone = true - progress = true + progress = true // TODO: ??? progressWaitReader(reader) + case Expirable.Capture.Expired => + writers.addFirst(writer) + writersLoopDone = true + progress = true } - if !writersLoopDone then - // we have reader, should return one back if we want to start again - // TODO: write test for this case - readers.addFirst(reader) } if (isClosed && (readers.isEmpty || writers.isEmpty) ) then - progress |= processWriteClose() + // progress |= processWriteClose() while(! doneReaders.isEmpty) { progress |= processDoneClose() } - progress |= processReadClose() + if (writers.isEmpty) + progress |= processReadClose() if (!progress) then if !checkLeaveStep() then progress = true diff --git a/jvm/src/main/scala/gopher/impl/PromiseChannel.scala b/jvm/src/main/scala/gopher/impl/PromiseChannel.scala index 4f2783ba..f88a6b00 100644 --- a/jvm/src/main/scala/gopher/impl/PromiseChannel.scala +++ b/jvm/src/main/scala/gopher/impl/PromiseChannel.scala @@ -4,6 +4,7 @@ import cps._ import gopher._ import java.util.concurrent.ConcurrentLinkedDeque import java.util.concurrent.ExecutorService +import java.util.concurrent.Executor import java.util.concurrent.atomic.AtomicReference import java.util.concurrent.atomic.AtomicInteger import java.util.concurrent.atomic.AtomicBoolean @@ -15,7 +16,7 @@ import scala.util.Failure /** * Channel is closed immediatly after successfull write. **/ - class PromiseChannel[F[_],A](override val gopherApi: JVMGopher[F], taskExecutor: ExecutorService) extends Channel[F,A,A]: + class PromiseChannel[F[_],A](override val gopherApi: JVMGopher[F], taskExecutor: Executor) extends Channel[F,A,A]: protected val readers = new ConcurrentLinkedDeque[Reader[A]]() protected val doneReaders = new ConcurrentLinkedDeque[Reader[Unit]]() @@ -31,7 +32,7 @@ import scala.util.Failure var done = false while(!done && !writer.isExpired) writer.capture() match - case Some((a,f)) => + case Expirable.Capture.Ready((a,f)) => val ar: AnyRef = a.asInstanceOf[AnyRef] // if (ref.compareAndSet(null,ar) && !closed.get() ) then closed.set(true) @@ -44,9 +45,9 @@ import scala.util.Failure taskExecutor.execute(() => f(Failure(new ChannelClosedException()))) writer.markUsed() done = true - case None => - if (!writer.isExpired) then + case Expirable.Capture.WaitChangeComplete => Thread.onSpinWait() + case Expirable.Capture.Expired => def addDoneReader(reader: Reader[Unit]): Unit = @@ -58,13 +59,13 @@ import scala.util.Failure var done = false while(!done & !reader.isExpired) { reader.capture() match - case Some(f) => + case Expirable.Capture.Ready(f) => reader.markUsed() taskExecutor.execute(()=>f(Success(()))) done = true - case None => - if (!reader.isExpired) - Thread.onSpinWait() + case Expirable.Capture.WaitChangeComplete => + Thread.onSpinWait() + case Expirable.Capture.Expired => } @@ -87,7 +88,7 @@ import scala.util.Failure if ! (r eq null) then while (!done && !r.isExpired) { r.capture() match - case Some(f) => + case Expirable.Capture.Ready(f) => done = true if (readed.compareAndSet(false,true)) then r.markUsed() @@ -101,12 +102,11 @@ import scala.util.Failure else r.markFree() readers.addLast(r) // called later after done - case None => - if (!r.isExpired) { - if (readers.isEmpty) + case Expirable.Capture.WaitChangeComplete => + if (readers.isEmpty) then Thread.onSpinWait() readers.addLast(r) - } + case Expirable.Capture.Expired => } } else if (closed.get()) then @@ -115,29 +115,29 @@ import scala.util.Failure def closeAll(): Unit = while(!doneReaders.isEmpty) { val r = doneReaders.poll() - if !((r eq null) || r.isExpired) then + if !(r eq null) then r.capture() match - case Some(f) => + case Expirable.Capture.Ready(f) => r.markUsed() taskExecutor.execute(()=>f(Success(()))) - case None => - if (!r.isExpired) then - if (doneReaders.isEmpty) then - Thread.onSpinWait() - doneReaders.addLast(r) + case Expirable.Capture.WaitChangeComplete => + if (doneReaders.isEmpty) then + Thread.onSpinWait() + doneReaders.addLast(r) + case Expirable.Capture.Expired => } while(!readers.isEmpty) { val r = readers.poll() - if (!(r eq null) && !r.isExpired) then + if !(r eq null) then r.capture() match - case Some(f) => + case Expirable.Capture.Ready(f) => r.markUsed() taskExecutor.execute(() => f(Failure(new ChannelClosedException))) - case None => - if (!r.isExpired) then - if (readers.isEmpty) then - Thread.onSpinWait() - readers.addLast(r) + case Expirable.Capture.WaitChangeComplete => + if (readers.isEmpty) then + Thread.onSpinWait() + readers.addLast(r) + case Expirable.Capture.Expired => } diff --git a/jvm/src/test/scala/gopher/channels/DuppedChannelsMultipleSuite.scala b/jvm/src/test/scala/gopher/channels/DuppedChannelsMultipleSuite.scala index b598437f..b15f20ff 100644 --- a/jvm/src/test/scala/gopher/channels/DuppedChannelsMultipleSuite.scala +++ b/jvm/src/test/scala/gopher/channels/DuppedChannelsMultipleSuite.scala @@ -10,24 +10,32 @@ import scala.concurrent.duration._ import scala.language.postfixOps import scala.util._ +import gopher.util.Debug + +import java.util.logging.{Level => LogLevel} + class DuppedChannelsMultipleSuite extends FunSuite { import scala.concurrent.ExecutionContext.Implicits.global given gopherApi: Gopher[Future] = SharedGopherAPI.apply[Future]() - val inMemoryLog = new java.util.concurrent.ConcurrentLinkedQueue[(Int, Long, String, Throwable)]() + val inMemoryLog = new Debug.InMemoryLog() test("on closing of main stream dupped outputs also closed N times.") { val N = 1000 var logIndex = 0 - gopherApi.setLogFun( (level,msg, ex) => inMemoryLog.add((logIndex, Thread.currentThread().getId(), msg,ex)) ) + gopherApi.setLogFun(Debug.inMemoryLogFun(inMemoryLog)) for(i <- 1 to N) { + inMemoryLog.clear() logIndex = i val ch = makeChannel[Int](1) + gopherApi.log(LogLevel.FINE, s"created origin ch=${ch}") val (in1, in2) = ch.dup() val f1 = async{ + gopherApi.log(LogLevel.FINE, s"before ch.write(1), ch=${ch}") ch.write(1) + gopherApi.log(LogLevel.FINE, s"before ch.close, ch=${ch}") ch.close() } val f = for{ fx <- f1 @@ -44,44 +52,16 @@ class DuppedChannelsMultipleSuite extends FunSuite { try { val r = Await.result(f, 30 seconds); }catch{ - case ex: TimeoutException => - showTraces(20) + case ex: Throwable => //: TimeoutException => + Debug.showTraces(20) println("---") - showInMemoryLog() + Debug.showInMemoryLog(inMemoryLog) throw ex } } } - def showTraces(maxTracesToShow: Int): Unit = { - val traces = Thread.getAllStackTraces(); - val it = traces.entrySet().iterator() - while(it.hasNext()) { - val e = it.next(); - println(e.getKey()); - val elements = e.getValue() - var sti = 0 - var wasPark = false - while(sti < elements.length && sti < maxTracesToShow && !wasPark) { - val st = elements(sti) - println(" "*10 + st) - sti = sti + 1; - wasPark = (st.getMethodName == "park") - } - } - } - - def showInMemoryLog(): Unit = { - while(!inMemoryLog.isEmpty) { - val r = inMemoryLog.poll() - if (r != null) { - println(r) - } - } - } - - } diff --git a/jvm/src/test/scala/gopher/stream/JVMBasicGeneratorSuite.scala b/jvm/src/test/scala/gopher/stream/JVMBasicGeneratorSuite.scala new file mode 100644 index 00000000..0e6ed3ef --- /dev/null +++ b/jvm/src/test/scala/gopher/stream/JVMBasicGeneratorSuite.scala @@ -0,0 +1,76 @@ +package gopher.stream + +import scala.concurrent.* +import scala.concurrent.duration.* +import scala.concurrent.ExecutionContext.Implicits.global + +import cps.* +import cps.monads.given + +import gopher.* +import gopher.util.Debug +import java.util.logging.{Level => LogLevel} + + +import munit.* + + +class JVMBasicGeneratorSuite extends FunSuite { + + val N = 10000 + + given Gopher[Future] = SharedGopherAPI[Future]() + + val inMemoryLog = new Debug.InMemoryLog() + + + summon[Gopher[Future]].setLogFun( Debug.inMemoryLogFun(inMemoryLog) ) + + + test("M small loop in gopher ReadChannel") { + + val M = 1000 + val N = 100 + + val folds: Seq[Future[Int]] = for(k <- 1 to M) yield { + val channel = asyncStream[ReadChannel[Future,Int]] { out => + var last = 0 + for(i <- 1 to N) { + out.emit(i) + last = i + //println("emitted: "+i) + //summon[Gopher[Future]].log(LogLevel.FINE, s"emitted $i in $k") + } + summon[Gopher[Future]].log(LogLevel.FINE, s"last $last in $k") + } + async[Future]{ + channel.fold(0)(_ + _) + } + } + + val expected = (1 to N).sum + + + val f = folds.foldLeft(Future.successful(())){ (s,e) => + s.flatMap{ r => + e.map{ x => + assert(x == expected) + } } + } + + try { + val r = Await.result(f, 30.seconds); + }catch{ + case ex: Throwable => //: TimeoutException => + Debug.showTraces(20) + println("---") + Debug.showInMemoryLog(inMemoryLog) + throw ex + } + + + } + + + +} \ No newline at end of file diff --git a/jvm/src/test/scala/gopher/util/Debug.scala b/jvm/src/test/scala/gopher/util/Debug.scala new file mode 100644 index 00000000..0950f80a --- /dev/null +++ b/jvm/src/test/scala/gopher/util/Debug.scala @@ -0,0 +1,47 @@ +package gopher.util + + +import java.util.logging.{Level => LogLevel} + + +object Debug { + + export java.util.logging.Level as LogLevel + + type InMemoryLog = java.util.concurrent.ConcurrentLinkedQueue[(Long, String, Throwable)] + + def inMemoryLogFun(inMemoryLog: InMemoryLog): (LogLevel, String, Throwable|Null) => Unit = + (level,msg, ex) => inMemoryLog.add((Thread.currentThread().getId(), msg,ex)) + + def showInMemoryLog(inMemoryLog: InMemoryLog): Unit = { + while(!inMemoryLog.isEmpty) { + val r = inMemoryLog.poll() + if (r != null) { + println(r) + } + } + } + + + def showTraces(maxTracesToShow: Int): Unit = { + val traces = Thread.getAllStackTraces(); + val it = traces.entrySet().iterator() + while(it.hasNext()) { + val e = it.next(); + println(e.getKey()); + val elements = e.getValue() + var sti = 0 + var wasPark = false + while(sti < elements.length && sti < maxTracesToShow && !wasPark) { + val st = elements(sti) + println(" "*10 + st) + sti = sti + 1; + wasPark = (st.getMethodName == "park") + } + } + } + + + + +} \ No newline at end of file diff --git a/shared/src/main/scala/gopher/ChannelClosedException.scala b/shared/src/main/scala/gopher/ChannelClosedException.scala index b59b7aee..3ffbda63 100644 --- a/shared/src/main/scala/gopher/ChannelClosedException.scala +++ b/shared/src/main/scala/gopher/ChannelClosedException.scala @@ -1,3 +1,5 @@ package gopher -class ChannelClosedException extends RuntimeException("channel is closed") +class ChannelClosedException( + debugInfo: String = "" +) extends RuntimeException(s"channel is closed. ${debugInfo}") diff --git a/shared/src/main/scala/gopher/Gopher.scala b/shared/src/main/scala/gopher/Gopher.scala index b30034e1..3e023bd8 100644 --- a/shared/src/main/scala/gopher/Gopher.scala +++ b/shared/src/main/scala/gopher/Gopher.scala @@ -1,12 +1,18 @@ package gopher import cps._ +import scala.concurrent.ExecutionContext import scala.concurrent.duration.Duration import scala.util._ import java.util.logging.{Level => LogLevel} +import java.util.concurrent.Executor +/** + * core of GopherAPI. + *Given instance of Gopher[F] need for using most of Gopher operations. + **/ trait Gopher[F[_]:CpsSchedulingMonad]: type Monad[X] = F[X] @@ -30,6 +36,8 @@ trait Gopher[F[_]:CpsSchedulingMonad]: def log(level: LogLevel, message: String): Unit = log(level,message, null) + def taskExecutionContext: ExecutionContext + protected[gopher] def logImpossible(ex: Throwable): Unit = log(LogLevel.WARNING, "impossible", ex) @@ -42,6 +50,7 @@ trait Gopher[F[_]:CpsSchedulingMonad]: } + def makeChannel[A](bufSize:Int = 0, autoClose: Boolean = false)(using g:Gopher[?]):Channel[g.Monad,A,A] = g.makeChannel(bufSize, autoClose) diff --git a/shared/src/main/scala/gopher/ReadChannel.scala b/shared/src/main/scala/gopher/ReadChannel.scala index b00af8b9..90b0b51e 100644 --- a/shared/src/main/scala/gopher/ReadChannel.scala +++ b/shared/src/main/scala/gopher/ReadChannel.scala @@ -6,6 +6,7 @@ import scala.util.Try import scala.util.Success import scala.util.Failure import scala.util.control.NonFatal +import scala.concurrent.ExecutionContext import scala.concurrent.duration.Duration import java.util.logging.{Level => LogLevel} @@ -197,7 +198,7 @@ trait ReadChannel[F[_], A]: def canExpire: Boolean = false def isExpired: Boolean = false - def capture(): Option[Try[A]=>Unit] = Some(f) + def capture(): Expirable.Capture[Try[A]=>Unit] = Expirable.Capture.Ready(f) def markUsed(): Unit = () def markFree(): Unit = () @@ -264,7 +265,7 @@ object ReadChannel: def unfold[S,F[_],A](s:S)(f:S => Option[(A,S)])(using Gopher[F]): ReadChannel[F,A] = unfoldAsync[S,F,A](s)( state => summon[Gopher[F]].asyncMonad.tryPure(f(state)) ) - def unfoldAsync[S,F[_],A](s:S)(f:S => F[Option[(A,S)]])(using Gopher[F]): ReadChannel[F,A]= + def unfoldAsync[S,F[_],A](s:S)(f:S => F[Option[(A,S)]])(using Gopher[F]): ReadChannel[F,A] = given asyncMonad: CpsSchedulingMonad[F] = summon[Gopher[F]].asyncMonad val retval = makeChannel[Try[A]]() summon[Gopher[F]].spawnAndLogFail(async{ @@ -281,8 +282,10 @@ object ReadChannel: } catch case NonFatal(ex) => + summon[Gopher[F]].log(LogLevel.FINE, s"exception (ch: $retval)", ex) retval.write(Failure(ex)) finally + summon[Gopher[F]].log(LogLevel.FINE, s"closing $retval") retval.close(); }) retval.map{ @@ -291,6 +294,20 @@ object ReadChannel: throw ex } + + + import cps.stream._ + + given emitAbsorber[F[_]: CpsSchedulingMonad,T](using gopherApi: Gopher[F]): BaseUnfoldCpsAsyncEmitAbsorber[ReadChannel[F,T],F,T]( + using gopherApi.asyncMonad, gopherApi.taskExecutionContext) with + + override type Element = T + + def unfold[S](s0:S)(f: S => F[Option[(T,S)]]): ReadChannel[F,T] = + val r: ReadChannel[F,T] = unfoldAsync(s0)(f) + r + + end ReadChannel diff --git a/shared/src/main/scala/gopher/SelectGroup.scala b/shared/src/main/scala/gopher/SelectGroup.scala index 0404c847..9f3b7306 100644 --- a/shared/src/main/scala/gopher/SelectGroup.scala +++ b/shared/src/main/scala/gopher/SelectGroup.scala @@ -175,16 +175,34 @@ class SelectGroup[F[_], S](api: Gopher[F]) extends SelectListeners[F,S,S]: type Element = A type State = S - override def capture(): Option[Try[A]=>Unit] = + val ready = Expirable.Capture.Ready[Try[A]=>Unit](v => { + timeoutScheduled.foreach(_.cancel()) + api.spawnAndLogFail(m.mapTry(action(v))(x => call(x))) + }) + + override def capture(): Expirable.Capture[Try[A]=>Unit] = + // fast path if waitState.compareAndSet(0,1) then - Some(v => { - timeoutScheduled.foreach(_.cancel()) - api.spawnAndLogFail( - m.mapTry(action(v))(x => call(x)) - ) - }) + ready else - None + var retval: Expirable.Capture[Try[A]=>Unit] = Expirable.Capture.Expired + while { + waitState.get() match + case 2 => retval = Expirable.Capture.Expired + false + case 1 => retval = Expirable.Capture.WaitChangeComplete + false + case 0 => // was just freed + if (waitState.compareAndSet(0,1)) then + retval = ready + false + else + true + case _ => // impossible. + throw new IllegalStateException("Imposible state of busy flag") + } do () + retval + @@ -195,16 +213,32 @@ class SelectGroup[F[_], S](api: Gopher[F]) extends SelectListeners[F,S,S]: type Element = A type State = S - override def capture(): Option[(A,Try[Unit]=>Unit)] = + val ready: Expirable.Capture.Ready[(A,Try[Unit]=>Unit)] = + Expirable.Capture.Ready((element, + (v:Try[Unit]) => { + timeoutScheduled.foreach(_.cancel()) + api.spawnAndLogFail(m.mapTry(action(v))(x=>call(x))) + } + )) + + override def capture(): Expirable.Capture[(A,Try[Unit]=>Unit)] = if waitState.compareAndSet(0,1) then - Some((element, (v:Try[Unit]) => { - timeoutScheduled.foreach(_.cancel()) - api.spawnAndLogFail( - m.mapTry(action(v))(x=>call(x)) - )} - )) + ready else - None + var retval: Expirable.Capture[(A,Try[Unit]=>Unit)] = Expirable.Capture.Expired + while{ + waitState.get() match + case 2 => false + case 1 => retval = Expirable.Capture.WaitChangeComplete + false + case 0 => + if (waitState.compareAndSet(0,1)) then + retval = ready + false + else + true + } do () + retval case class TimeoutRecord(duration: FiniteDuration, diff --git a/shared/src/main/scala/gopher/impl/AppendReadChannel.scala b/shared/src/main/scala/gopher/impl/AppendReadChannel.scala index 8a01bd26..e58cb812 100644 --- a/shared/src/main/scala/gopher/impl/AppendReadChannel.scala +++ b/shared/src/main/scala/gopher/impl/AppendReadChannel.scala @@ -33,7 +33,7 @@ case class AppendReadChannel[F[_],A](x: ReadChannel[F,A], y: ReadChannel[F,A]) e def isExpired: Boolean = nested.isExpired - def capture():Option[Try[A]=>Unit] = + def capture():Expirable.Capture[Try[A]=>Unit] = nested.capture().map{ readFun => { case r@Success(a) => if (inUsage.get()) then diff --git a/shared/src/main/scala/gopher/impl/DuppedInput.scala b/shared/src/main/scala/gopher/impl/DuppedInput.scala index 2485c76b..04e1632c 100644 --- a/shared/src/main/scala/gopher/impl/DuppedInput.scala +++ b/shared/src/main/scala/gopher/impl/DuppedInput.scala @@ -22,16 +22,20 @@ class DuppedInput[F[_],A](origin:ReadChannel[F,A], bufSize: Int=1)(using api:Gop given CpsSchedulingMonad[F] = api.asyncMonad - val runner = SelectLoop[F](api). - onRead(origin.done){ _ => - sink1.close() - sink2.close() - false - }.onReadAsync(origin){a => async{ - val f1 = sink1.write(a) - val f2 = sink2.write(a) - true - }}.runAsync() - api.spawnAndLogFail(runner) + val runner = async{ + while + origin.optRead() match + case Some(a) => + sink1.write(a) + sink2.write(a) + true + case None => + false + do () + sink1.close() + sink2.close() + } + + api.spawnAndLogFail(runner) } diff --git a/shared/src/main/scala/gopher/impl/Expirable.scala b/shared/src/main/scala/gopher/impl/Expirable.scala index 6a6e626c..0a5a3d08 100644 --- a/shared/src/main/scala/gopher/impl/Expirable.scala +++ b/shared/src/main/scala/gopher/impl/Expirable.scala @@ -26,7 +26,7 @@ trait Expirable[A]: /** * capture object, and after this we can or use one (markUsed will be called) or abandon (markFree) **/ - def capture(): Option[A] + def capture(): Expirable.Capture[A] /** * Called when we submitt to task executor readFunction and now is safe to make exprire all other readers/writers in the @@ -35,7 +35,26 @@ trait Expirable[A]: def markUsed(): Unit /** - * Called when it was a race condition and we can't use captured function. + * Called when we can't use captured function (i.e. get function but ). **/ def markFree(): Unit + + +object Expirable: + + enum Capture[+A]: + case Ready(value: A) + case WaitChangeComplete + case Expired + + def map[B](f: A=>B): Capture[B] = + this match + case Ready(a) => Ready(f(a)) + case WaitChangeComplete => WaitChangeComplete + case Expired => Expired + + def foreach(f: A=>Unit): Unit = + this match + case Ready(a) => f(a) + case _ => \ No newline at end of file diff --git a/shared/src/main/scala/gopher/impl/FilteredReadChannel.scala b/shared/src/main/scala/gopher/impl/FilteredReadChannel.scala index 4c0a221f..916debaa 100644 --- a/shared/src/main/scala/gopher/impl/FilteredReadChannel.scala +++ b/shared/src/main/scala/gopher/impl/FilteredReadChannel.scala @@ -27,7 +27,7 @@ class FilteredReadChannel[F[_],A](internal: ReadChannel[F,A], p: A=>Boolean) ext fun(Failure(ex)) } - override def capture(): Option[Try[A]=>Unit] = + override def capture(): Expirable.Capture[Try[A]=>Unit] = nested.capture().map{ fun => wrappedFun(fun) } @@ -82,7 +82,7 @@ class FilteredAsyncReadChannel[F[_],A](internal: ReadChannel[F,A], p: A=>F[Boole fun(Failure(ex)) } - override def capture(): Option[Try[A]=>Unit] = + override def capture(): Expirable.Capture[Try[A]=>Unit] = nested.capture().map{ fun => wrappedFun(fun) } diff --git a/shared/src/main/scala/gopher/impl/MappedReadChannel.scala b/shared/src/main/scala/gopher/impl/MappedReadChannel.scala index 8641d663..8fe111c4 100644 --- a/shared/src/main/scala/gopher/impl/MappedReadChannel.scala +++ b/shared/src/main/scala/gopher/impl/MappedReadChannel.scala @@ -22,7 +22,7 @@ class MappedReadChannel[F[_],A, B](internal: ReadChannel[F,A], f: A=> B) extends } //TODO: think, are we want to pass error to the next level ? - override def capture(): Option[Try[A]=>Unit] = + override def capture(): Expirable.Capture[Try[A]=>Unit] = nested.capture().map{ fun => wrappedFun(fun) } @@ -67,7 +67,7 @@ class MappedAsyncReadChannel[F[_],A, B](internal: ReadChannel[F,A], f: A=> F[B]) } //TODO: think, are we want to pass error to the next level ? - override def capture(): Option[Try[A]=>Unit] = + override def capture(): Expirable.Capture[Try[A]=>Unit] = nested.capture().map{ fun => wrappedFun(fun) } diff --git a/shared/src/main/scala/gopher/impl/OrReadChannel.scala b/shared/src/main/scala/gopher/impl/OrReadChannel.scala index f3ed1f8d..3d6367c6 100644 --- a/shared/src/main/scala/gopher/impl/OrReadChannel.scala +++ b/shared/src/main/scala/gopher/impl/OrReadChannel.scala @@ -60,14 +60,17 @@ case class OrReadChannel[F[_],A](x: ReadChannel[F,A], y: ReadChannel[F,A]) exten } } - def capture(fromChannel: ReadChannel[F,A]): Option[Try[B]=>Unit] = + def capture(fromChannel: ReadChannel[F,A]): Expirable.Capture[Try[B]=>Unit] = if inUse.compareAndSet(null,fromChannel) then nested.capture() match - case Some(readFun) => Some(intercept(readFun)) - case None => inUse.set(null) - None + case Expirable.Capture.Ready(readFun) => Expirable.Capture.Ready(intercept(readFun)) + case Expirable.Capture.WaitChangeComplete => + inUse.set(null) + Expirable.Capture.WaitChangeComplete + case Expirable.Capture.Expired => inUse.set(null) + Expirable.Capture.Expired else - None + Expirable.Capture.WaitChangeComplete def markFree(fromChannel: ReadChannel[F,A]): Unit = if(inUse.get() eq fromChannel) then @@ -103,7 +106,7 @@ case class OrReadChannel[F[_],A](x: ReadChannel[F,A], y: ReadChannel[F,A]) exten class WrappedReader[B](common: CommonBase[B], owner: ReadChannel[F,A]) extends Reader[B] { - def capture(): Option[Try[B]=>Unit] = + def capture(): Expirable.Capture[Try[B]=>Unit] = common.capture(owner) def canExpire: Boolean = common.canExpire diff --git a/shared/src/main/scala/gopher/impl/Writer.scala b/shared/src/main/scala/gopher/impl/Writer.scala index 13e162c0..e8d4b8b1 100644 --- a/shared/src/main/scala/gopher/impl/Writer.scala +++ b/shared/src/main/scala/gopher/impl/Writer.scala @@ -11,7 +11,7 @@ class SimpleWriter[A](a:A, f: Try[Unit]=>Unit) extends Writer[A]: def isExpired: Boolean = false - def capture(): Option[(A,Try[Unit]=>Unit)] = Some((a,f)) + def capture(): Expirable.Capture[(A,Try[Unit]=>Unit)] = Expirable.Capture.Ready((a,f)) def markUsed(): Unit = () diff --git a/shared/src/main/scala/gopher/impl/WriterWithExpireTime.scala b/shared/src/main/scala/gopher/impl/WriterWithExpireTime.scala index e3950895..c01341e1 100644 --- a/shared/src/main/scala/gopher/impl/WriterWithExpireTime.scala +++ b/shared/src/main/scala/gopher/impl/WriterWithExpireTime.scala @@ -15,7 +15,7 @@ class SimpleWriterWithExpireTime[A](a:A, f: Try[Unit] => Unit, expireTimeMillis: //TODO: way to mock current time System.currentTimeMillis >= expireTimeMillis - def capture(): Option[(A,Try[Unit]=>Unit)] = Some((a,f)) + def capture(): Expirable.Capture[(A,Try[Unit]=>Unit)] = Expirable.Capture.Ready((a,f)) def markUsed(): Unit = () @@ -29,8 +29,8 @@ class NesteWriterWithExpireTime[A](nested: Writer[A], expireTimeMillis: Long) ex def isExpired: Boolean = (System.currentTimeMillis >= expireTimeMillis) || nested.isExpired - def capture(): Option[(A,Try[Unit]=>Unit)] = - if (isExpired) None else nested.capture() + def capture(): Expirable.Capture[(A,Try[Unit]=>Unit)] = + if (isExpired) Expirable.Capture.Expired else nested.capture() def markUsed(): Unit = nested.markUsed() @@ -48,8 +48,11 @@ class NestedWriterWithExpireTimeThrowing[F[_],A](nested: Writer[A], expireTimeMi def isExpired: Boolean = (gopherApi.time.now().toMillis >= expireTimeMillis) || nested.isExpired - def capture(): Option[(A,Try[Unit]=>Unit)] = - nested.capture() + def capture(): Expirable.Capture[(A,Try[Unit]=>Unit)] = + if (gopherApi.time.now().toMillis > expireTimeMillis) then + Expirable.Capture.Expired + else + nested.capture() def markUsed(): Unit = scheduledThrow.cancel() @@ -63,14 +66,18 @@ class NestedWriterWithExpireTimeThrowing[F[_],A](nested: Writer[A], expireTimeMi if (gopherApi.time.now().toMillis > expireTimeMillis) then if (!nested.isExpired) then nested.capture() match - case Some((a,f)) => + case Expirable.Capture.Ready((a,f)) => nested.markUsed() try f(Failure(new TimeoutException())) catch case ex: Throwable => ex.printStackTrace() - case None => + case Expirable.Capture.WaitChangeComplete => + gopherApi.time.schedule( + () => checkExpire(), + FiniteDuration(100, TimeUnit.MILLISECONDS) ) + case Expirable.Capture.Expired => // none, will be colled after markFree is needed. diff --git a/shared/src/test/scala/gopher/channels/DuppedChannelsSuite.scala b/shared/src/test/scala/gopher/channels/DuppedChannelsSuite.scala index 93c392a0..9656e724 100644 --- a/shared/src/test/scala/gopher/channels/DuppedChannelsSuite.scala +++ b/shared/src/test/scala/gopher/channels/DuppedChannelsSuite.scala @@ -58,7 +58,7 @@ class DuppedChannelsSuite extends FunSuite { x <- in1.aread() r <- in1.aread().transformWith { case Success(u) => - Future failed new IllegalStateException("Mist be closed") + Future failed new IllegalStateException("Must be closed") case Failure(u) => Future successful (assert(x == 1)) } diff --git a/shared/src/test/scala/gopher/stream/BasicGeneratorSuite.scala b/shared/src/test/scala/gopher/stream/BasicGeneratorSuite.scala new file mode 100644 index 00000000..ec1c7115 --- /dev/null +++ b/shared/src/test/scala/gopher/stream/BasicGeneratorSuite.scala @@ -0,0 +1,95 @@ +package gopher.stream + +import scala.concurrent.* +import scala.concurrent.ExecutionContext.Implicits.global + +import cps.* +import cps.monads.given + +import gopher.* +import gopher.util.Debug + +import munit.* + + +class BasicGeneratorSuite extends FunSuite { + + val N = 10000 + + given Gopher[Future] = SharedGopherAPI[Future]() + + val inMemoryLog = new Debug.InMemoryLog() + + + summon[Gopher[Future]].setLogFun( Debug.inMemoryLogFun(inMemoryLog) ) + + test("simple loop in gopher ReadChannel") { + + val channel = asyncStream[ReadChannel[Future,Int]] { out => + for(i <- 1 to N) { + out.emit(i) + } + } + + + async[Future] { + val r = channel.fold(0)(_ + _) + assert(r == (1 to N).sum) + } + + } + + test("M small loop in gopher ReadChannel") { + + val M = 1000 + val N = 100 + + val folds: Seq[Future[Int]] = for(i <- 1 to M) yield { + val channel = asyncStream[ReadChannel[Future,Int]] { out => + for(i <- 1 to N) { + out.emit(i) + //println("emitted: "+i) + } + } + async[Future]{ + channel.fold(0)(_ + _) + } + } + + val expected = (1 to N).sum + + + folds.foldLeft(Future.successful(())){ (s,e) => + s.flatMap{ r => + e.map{ x => + assert(x == expected) + } } + } + + + + + } + + + /* + test("exception should break loop in gopher generator") { + val channel = asyncStream[ReadChannel[Future, Int]] { out => + for(i <- 1 to N) { + if (i == N/2) then + throw new RuntimeException("bye") + out.emit(i) + } + } + + val res = async[Future] { + val r = channel.fold(0)(_ + _) + assert(r == (1 to N).sum) + } + + res.failed.map(ex => assert(ex.getMessage()=="bye")) + + }*/ + + +} \ No newline at end of file From 90cdad3b03ab2372a48a21bb43a1f46485050fd5 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Sun, 5 Sep 2021 13:53:26 +0300 Subject: [PATCH 107/161] added mima plugin, updated depemdencies --- build.sbt | 5 ++-- project/plugins.sbt | 2 +- shared/src/main/scala/gopher/Channel.scala | 3 +++ .../src/main/scala/gopher/ReadChannel.scala | 26 +++++++++++++++++-- .../src/main/scala/gopher/WriteChannel.scala | 1 + .../gopher/stream/BasicGeneratorSuite.scala | 9 +++---- 6 files changed, 35 insertions(+), 11 deletions(-) diff --git a/build.sbt b/build.sbt index 3bc2f492..b5b5dccf 100644 --- a/build.sbt +++ b/build.sbt @@ -2,7 +2,7 @@ val dottyVersion = "3.0.1" //val dottyVersion = dottyLatestNightlyBuild.get -ThisBuild/version := "2.0.7-SNAPSHOT" +ThisBuild/version := "2.1.0" ThisBuild/versionScheme := Some("semver-spec") val sharedSettings = Seq( @@ -10,7 +10,7 @@ val sharedSettings = Seq( scalaVersion := dottyVersion, name := "scala-gopher", resolvers += "Local Ivy Repository" at "file://"+Path.userHome.absolutePath+"/.ivy2/local", - libraryDependencies += "com.github.rssh" %%% "dotty-cps-async" % "0.9.3-SNAPSHOT", + libraryDependencies += "com.github.rssh" %%% "dotty-cps-async" % "0.9.3", libraryDependencies += "org.scalameta" %%% "munit" % "0.7.27" % Test, ) @@ -42,6 +42,7 @@ lazy val gopher = crossProject(JSPlatform, JVMPlatform) s"-javaagent:${System.getProperty("user.home")}/.ivy2/local/com.github.rssh/trackedfuture_3/0.5.0/jars/trackedfuture_3-assembly.jar" ) */ + mimaPreviousArtifacts := Set( "com.github.rssh" %% "scala-gopher" % "2.0.6") ).jsSettings( libraryDependencies += ("org.scala-js" %%% "scalajs-java-logging" % "1.0.0").cross(CrossVersion.for3Use2_13), // TODO: switch to ModuleES ? diff --git a/project/plugins.sbt b/project/plugins.sbt index a5c778e6..e0e123cc 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -3,4 +3,4 @@ addSbtPlugin("com.typesafe.sbt" % "sbt-site" % "1.4.1") addSbtPlugin("com.typesafe.sbt" % "sbt-ghpages" % "0.6.3") addSbtPlugin("org.portable-scala" % "sbt-scalajs-crossproject" % "1.0.0") addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.7.0") - +addSbtPlugin("com.typesafe" % "sbt-mima-plugin" % "1.0.0") diff --git a/shared/src/main/scala/gopher/Channel.scala b/shared/src/main/scala/gopher/Channel.scala index a281add7..817765e2 100644 --- a/shared/src/main/scala/gopher/Channel.scala +++ b/shared/src/main/scala/gopher/Channel.scala @@ -47,6 +47,9 @@ object Channel: case class FRead[F[_],A](a:A, ch: F[A]) case class Write[F[_],A](a: A, ch: WriteChannel[F,A]) + import cps.stream._ + + end Channel diff --git a/shared/src/main/scala/gopher/ReadChannel.scala b/shared/src/main/scala/gopher/ReadChannel.scala index 90b0b51e..53099e88 100644 --- a/shared/src/main/scala/gopher/ReadChannel.scala +++ b/shared/src/main/scala/gopher/ReadChannel.scala @@ -12,13 +12,17 @@ import scala.concurrent.duration.Duration import java.util.logging.{Level => LogLevel} /** - * ReadChannel: Interface providing reading API. + * ReadChannel: Interface providing asynchronous reading API. * **/ trait ReadChannel[F[_], A]: thisReadChannel => + /** + * Special type which is used in select statement. + *@see [gopher.Select] + **/ type read = A def gopherApi: Gopher[F] @@ -45,7 +49,8 @@ trait ReadChannel[F[_], A]: /** * blocked read: if currently not element available - wait for one. - * Can be used only inside async block + * Can be used only inside async block. + * If stream is closed and no values to read left in the stream - throws StreamClosedException **/ transparent inline def read(): A = await(aread())(using rAsyncMonad) @@ -74,9 +79,18 @@ trait ReadChannel[F[_], A]: b.result() } + /** + * take first `n` elements. + * should be called inside async block. + **/ transparent inline def take(n: Int): IndexedSeq[A] = await(atake(n))(using rAsyncMonad) + /** + * read value and return future with + * - Some(value) if value is available to read + * - None if stream is closed. + **/ def aOptRead(): F[Option[A]] = asyncMonad.adoptCallbackStyle( f => addReader(SimpleReader{ x => x match @@ -86,6 +100,13 @@ trait ReadChannel[F[_], A]: }) ) + /** + * read value and return + * - Some(value) if value is available to read + * - None if stream is closed. + * + * should be called inside async block. + **/ transparent inline def optRead(): Option[A] = await(aOptRead())(using rAsyncMonad) def foreach_async(f: A=>F[Unit]): F[Unit] = @@ -112,6 +133,7 @@ trait ReadChannel[F[_], A]: transparent inline def foreach(inline f: A=>Unit): Unit = await(aforeach(f))(using rAsyncMonad) + def map[B](f: A=>B): ReadChannel[F,B] = new MappedReadChannel(this, f) diff --git a/shared/src/main/scala/gopher/WriteChannel.scala b/shared/src/main/scala/gopher/WriteChannel.scala index b222b270..a0e25cd6 100644 --- a/shared/src/main/scala/gopher/WriteChannel.scala +++ b/shared/src/main/scala/gopher/WriteChannel.scala @@ -7,6 +7,7 @@ import scala.annotation.targetName import scala.concurrent.duration.FiniteDuration import scala.util.Try + trait WriteChannel[F[_], A]: type write = A diff --git a/shared/src/test/scala/gopher/stream/BasicGeneratorSuite.scala b/shared/src/test/scala/gopher/stream/BasicGeneratorSuite.scala index ec1c7115..5e49b682 100644 --- a/shared/src/test/scala/gopher/stream/BasicGeneratorSuite.scala +++ b/shared/src/test/scala/gopher/stream/BasicGeneratorSuite.scala @@ -66,13 +66,9 @@ class BasicGeneratorSuite extends FunSuite { } } } - - - + } - - /* test("exception should break loop in gopher generator") { val channel = asyncStream[ReadChannel[Future, Int]] { out => for(i <- 1 to N) { @@ -89,7 +85,8 @@ class BasicGeneratorSuite extends FunSuite { res.failed.map(ex => assert(ex.getMessage()=="bye")) - }*/ + } + } \ No newline at end of file From 371f1cf9c9ca3317edae57a75df2971d7157edf4 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Sun, 5 Sep 2021 13:58:15 +0300 Subject: [PATCH 108/161] updated published version --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2ad690a6..c6e75a80 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ For scala 3: - libraryDependencies += "com.github.rssh" %% "scala-gopher" % "2.0.6" + libraryDependencies += "com.github.rssh" %% "scala-gopher" % "2.1.0" For scala2: From 7cabe7f795e98c5fdbc762086cc28ba43f018700 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Sun, 5 Sep 2021 13:58:28 +0300 Subject: [PATCH 109/161] updated dotty version --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index b5b5dccf..9b64dbf7 100644 --- a/build.sbt +++ b/build.sbt @@ -1,5 +1,5 @@ //val dottyVersion = "3.0.0-RC2-bin-SNAPSHOT" -val dottyVersion = "3.0.1" +val dottyVersion = "3.0.2" //val dottyVersion = dottyLatestNightlyBuild.get ThisBuild/version := "2.1.0" From 6455367555f25850bfa3e47a7123bf94ed917390 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Sat, 9 Oct 2021 18:46:46 +0300 Subject: [PATCH 110/161] removed unused variable --- build.sbt | 4 ++-- jvm/src/main/scala/gopher/JVMGopher.scala | 2 +- shared/src/main/scala/gopher/Gopher.scala | 8 +++++++- shared/src/main/scala/gopher/Time.scala | 4 ++++ 4 files changed, 14 insertions(+), 4 deletions(-) diff --git a/build.sbt b/build.sbt index 9b64dbf7..ae8f5482 100644 --- a/build.sbt +++ b/build.sbt @@ -2,7 +2,7 @@ val dottyVersion = "3.0.2" //val dottyVersion = dottyLatestNightlyBuild.get -ThisBuild/version := "2.1.0" +ThisBuild/version := "2.1.1-SNAPSHOT" ThisBuild/versionScheme := Some("semver-spec") val sharedSettings = Seq( @@ -42,7 +42,7 @@ lazy val gopher = crossProject(JSPlatform, JVMPlatform) s"-javaagent:${System.getProperty("user.home")}/.ivy2/local/com.github.rssh/trackedfuture_3/0.5.0/jars/trackedfuture_3-assembly.jar" ) */ - mimaPreviousArtifacts := Set( "com.github.rssh" %% "scala-gopher" % "2.0.6") + mimaPreviousArtifacts := Set( "com.github.rssh" %% "scala-gopher" % "2.1.0") ).jsSettings( libraryDependencies += ("org.scala-js" %%% "scalajs-java-logging" % "1.0.0").cross(CrossVersion.for3Use2_13), // TODO: switch to ModuleES ? diff --git a/jvm/src/main/scala/gopher/JVMGopher.scala b/jvm/src/main/scala/gopher/JVMGopher.scala index eac8bab6..01fdc146 100644 --- a/jvm/src/main/scala/gopher/JVMGopher.scala +++ b/jvm/src/main/scala/gopher/JVMGopher.scala @@ -54,7 +54,7 @@ object JVMGopher extends GopherAPI: case jcfg:JVMGopherConfig => jcfg new JVMGopher[F](jvmConfig) - lazy val timer = new Timer("gopher") + //lazy val timer = new Timer("gopher") lazy val scheduledExecutor = Executors.newScheduledThreadPool(1) diff --git a/shared/src/main/scala/gopher/Gopher.scala b/shared/src/main/scala/gopher/Gopher.scala index 3e023bd8..fa749783 100644 --- a/shared/src/main/scala/gopher/Gopher.scala +++ b/shared/src/main/scala/gopher/Gopher.scala @@ -11,13 +11,19 @@ import java.util.concurrent.Executor /** * core of GopherAPI. - *Given instance of Gopher[F] need for using most of Gopher operations. + * Given instance of Gopher[F] need for using most of Gopher operations. + * + * Gopehr API ia a framework, which implements CSP (Communication Sequence Process). + * Process here - scala units of execution (i.e. ) **/ trait Gopher[F[_]:CpsSchedulingMonad]: type Monad[X] = F[X] + + def asyncMonad: CpsSchedulingMonad[F] = summon[CpsSchedulingMonad[F]] + def makeChannel[A](bufSize:Int = 0, autoClose: Boolean = false): Channel[F,A,A] diff --git a/shared/src/main/scala/gopher/Time.scala b/shared/src/main/scala/gopher/Time.scala index 6038c613..cdf89d06 100644 --- a/shared/src/main/scala/gopher/Time.scala +++ b/shared/src/main/scala/gopher/Time.scala @@ -21,6 +21,10 @@ import java.util.TimerTask */ abstract class Time[F[_]](gopherAPI: Gopher[F]) { + /** + * type for using in `select` paterns. + * @see [gopher.Select] + **/ type after = FiniteDuration def after(duration: FiniteDuration): ReadChannel[F,FiniteDuration] = From f8225ac66b830b622c18e5dd052f67408a0c3588 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Sun, 10 Oct 2021 10:25:47 +0300 Subject: [PATCH 111/161] better scaladoc --- build.sbt | 8 +++- shared/src/main/scala/gopher/Gopher.scala | 52 ++++++++++++++++++++--- shared/src/main/scala/gopher/Select.scala | 5 ++- shared/src/main/scala/gopher/Time.scala | 22 +++++++++- 4 files changed, 76 insertions(+), 11 deletions(-) diff --git a/build.sbt b/build.sbt index ae8f5482..9716523b 100644 --- a/build.sbt +++ b/build.sbt @@ -1,5 +1,5 @@ //val dottyVersion = "3.0.0-RC2-bin-SNAPSHOT" -val dottyVersion = "3.0.2" +val dottyVersion = "3.1.0-RC2" //val dottyVersion = dottyLatestNightlyBuild.get ThisBuild/version := "2.1.1-SNAPSHOT" @@ -42,12 +42,18 @@ lazy val gopher = crossProject(JSPlatform, JVMPlatform) s"-javaagent:${System.getProperty("user.home")}/.ivy2/local/com.github.rssh/trackedfuture_3/0.5.0/jars/trackedfuture_3-assembly.jar" ) */ + Compile / doc / scalacOptions := Seq("-groups", + "-source-links:shared=github://rssh/scala-gopher/master#shared", + "-source-links:jvm=github://rssh/scala-gopher/master#jvm"), mimaPreviousArtifacts := Set( "com.github.rssh" %% "scala-gopher" % "2.1.0") ).jsSettings( libraryDependencies += ("org.scala-js" %%% "scalajs-java-logging" % "1.0.0").cross(CrossVersion.for3Use2_13), // TODO: switch to ModuleES ? scalaJSLinkerConfig ~= { _.withModuleKind(ModuleKind.CommonJSModule) }, scalaJSUseMainModuleInitializer := true, + Compile / doc / scalacOptions := Seq("-groups", + "-source-links:shared=github://rssh/scala-gopher/master#shared", + "-source-links:js=github://rssh/scala-gopher/master#js"), ) lazy val GopherJVM = config("gopher.jvm") diff --git a/shared/src/main/scala/gopher/Gopher.scala b/shared/src/main/scala/gopher/Gopher.scala index fa749783..ef12067f 100644 --- a/shared/src/main/scala/gopher/Gopher.scala +++ b/shared/src/main/scala/gopher/Gopher.scala @@ -10,31 +10,57 @@ import java.util.concurrent.Executor /** - * core of GopherAPI. - * Given instance of Gopher[F] need for using most of Gopher operations. + * core of Gopher API. Given instance of Gopher[F] need for using most of Gopher operations. * - * Gopehr API ia a framework, which implements CSP (Communication Sequence Process). - * Process here - scala units of execution (i.e. ) + * Gopher is a framework, which implements CSP (Communication Sequence Process). + * Process here - scala units of execution (i.e. functions, blok of code, etc). + * Communication channels represented by [gopher.Channel] + * + * @see [gopher.Channel] + * @see [gopher#select] **/ trait Gopher[F[_]:CpsSchedulingMonad]: type Monad[X] = F[X] - + /** + * Monad which control asynchronic execution. + * The main is scheduling: i.e. ability to submit monadic expression to scheduler + * and know that this monadic expression will be evaluated. + **/ def asyncMonad: CpsSchedulingMonad[F] = summon[CpsSchedulingMonad[F]] - + /** + * Create Read/Write channel. + * @param bufSize - size of buffer. If it is zero, the channel is unbuffered. (i.e. writer is blocked until reader start processing). + * @param autoClose - close after first message was written to channel. + * @see [gopher.Channel] + **/ def makeChannel[A](bufSize:Int = 0, autoClose: Boolean = false): Channel[F,A,A] + /** + * Create channel where you can write only one element. + * @see [gopher.Channel] + **/ def makeOnceChannel[A](): Channel[F,A,A] = makeChannel[A](1,true) + /*** + *Create a select statement, which used for choosing one action from a set of potentially concurrent asynchronics events. + *[@see gopher.Select] + **/ def select: Select[F] = new Select[F](this) + /** + * get an object with time operations. + **/ def time: Time[F] + /** + * set logging function, which output internal diagnostics and errors from spawned processes. + **/ def setLogFun(logFun:(LogLevel, String, Throwable|Null) => Unit): ((LogLevel, String, Throwable|Null) => Unit) def log(level: LogLevel, message: String, ex: Throwable| Null): Unit @@ -55,18 +81,30 @@ trait Gopher[F[_]:CpsSchedulingMonad]: () } +end Gopher - + +/** +* Create Read/Write channel. +* @param bufSize - size of buffer. If it is zero, the channel is unbuffered. (i.e. writer is blocked until reader start processing). +* @param autoClose - close after first message was written to channel. +* @see [gopher.Channel] +**/ def makeChannel[A](bufSize:Int = 0, autoClose: Boolean = false)(using g:Gopher[?]):Channel[g.Monad,A,A] = g.makeChannel(bufSize, autoClose) + def makeOnceChannel[A]()(using g:Gopher[?]): Channel[g.Monad,A,A] = g.makeOnceChannel[A]() + def select(using g:Gopher[?]):Select[g.Monad] = g.select +/** + * represent `F[_]` as read channel. + **/ def futureInput[F[_],A](f: F[A])(using g: Gopher[F]): ReadChannel[F,A] = val ch = g.makeOnceChannel[Try[A]]() g.spawnAndLogFail{ diff --git a/shared/src/main/scala/gopher/Select.scala b/shared/src/main/scala/gopher/Select.scala index 221a85cd..e4984087 100644 --- a/shared/src/main/scala/gopher/Select.scala +++ b/shared/src/main/scala/gopher/Select.scala @@ -31,7 +31,7 @@ class Select[F[_]](api: Gopher[F]): SelectMacro.onceImpl[F,A]('pf, 'api ) } - /*** + /** * create select groop *@see [gopher.SelectGroup] **/ @@ -39,6 +39,9 @@ class Select[F[_]](api: Gopher[F]): def once[S]: SelectGroup[F,S] = new SelectGroup[F,S](api) + /** + * create Select Loop. + **/ def loop: SelectLoop[F] = new SelectLoop[F](api) diff --git a/shared/src/main/scala/gopher/Time.scala b/shared/src/main/scala/gopher/Time.scala index cdf89d06..010af07f 100644 --- a/shared/src/main/scala/gopher/Time.scala +++ b/shared/src/main/scala/gopher/Time.scala @@ -27,6 +27,9 @@ abstract class Time[F[_]](gopherAPI: Gopher[F]) { **/ type after = FiniteDuration + /** + * return channel, then after `duration` ellapses, send signal to this channel. + **/ def after(duration: FiniteDuration): ReadChannel[F,FiniteDuration] = { val ch = gopherAPI.makeOnceChannel[FiniteDuration]() @@ -39,6 +42,9 @@ abstract class Time[F[_]](gopherAPI: Gopher[F]) { ch } + /** + * return future which will be filled after time will ellapse. + **/ def asleep(duration: FiniteDuration): F[FiniteDuration] = { var fun: Try[FiniteDuration] => Unit = _ => () @@ -51,6 +57,9 @@ abstract class Time[F[_]](gopherAPI: Gopher[F]) { retval } + /** + * synonim for `await(asleep(duration))`. Should be used inside async block. + **/ transparent inline def sleep(duration: FiniteDuration): FiniteDuration = given CpsSchedulingMonad[F] = gopherAPI.asyncMonad await(asleep(duration)) @@ -66,7 +75,9 @@ abstract class Time[F[_]](gopherAPI: Gopher[F]) { newTicker(duration).channel } - + /** + * ticker which hold channel with expirable tick messages and iterface to stop one. + **/ class Ticker(duration: FiniteDuration) { val channel = gopherAPI.makeChannel[FiniteDuration](0).withExpiration(duration, false) @@ -97,6 +108,10 @@ abstract class Time[F[_]](gopherAPI: Gopher[F]) { } + /** + * create ticker with given `duration` between ticks. + *@see [gopher.Time.Ticker] + **/ def newTicker(duration: FiniteDuration): Ticker = { new Ticker(duration) @@ -108,7 +123,7 @@ abstract class Time[F[_]](gopherAPI: Gopher[F]) { /** - * Low lwvel interface for scheduler + * Low level interface for scheduler */ def schedule(fun: () => Unit, delay: FiniteDuration): Time.Scheduled @@ -131,6 +146,9 @@ object Time: type after = FiniteDuration + /** + * return channl on which event will be delivered after `duration` + **/ def after[F[_]](duration: FiniteDuration)(using Gopher[F]): ReadChannel[F,FiniteDuration] = summon[Gopher[F]].time.after(duration) From 4a9fc0d96a6efad652f43b238b10da5f223491d4 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Sun, 10 Oct 2021 10:30:23 +0300 Subject: [PATCH 112/161] publish scaladoc on github --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index c6e75a80..c1921f77 100644 --- a/README.md +++ b/README.md @@ -250,6 +250,7 @@ val multiplexed = select amap { ## 2.0.x implementation * source code: https://github.com/rssh/scala-gopher +* scaladoc: https://rssh.github.io/scala-gopher/api/jvm/index.html ## [0.99.x] implementation: * source code: https://github.com/rssh/scala-gopher/tree/0.99x From af1a4926e845d68e49d7c5cd1abe98fda7b40a89 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Sun, 10 Oct 2021 10:35:58 +0300 Subject: [PATCH 113/161] fix reference format in scaladoc --- jvm/src/main/scala/gopher/JVMGopher.scala | 2 -- shared/src/main/scala/gopher/Channel.scala | 5 +++++ shared/src/main/scala/gopher/Gopher.scala | 7 ++++--- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/jvm/src/main/scala/gopher/JVMGopher.scala b/jvm/src/main/scala/gopher/JVMGopher.scala index 01fdc146..4598eeb0 100644 --- a/jvm/src/main/scala/gopher/JVMGopher.scala +++ b/jvm/src/main/scala/gopher/JVMGopher.scala @@ -54,8 +54,6 @@ object JVMGopher extends GopherAPI: case jcfg:JVMGopherConfig => jcfg new JVMGopher[F](jvmConfig) - //lazy val timer = new Timer("gopher") - lazy val scheduledExecutor = Executors.newScheduledThreadPool(1) lazy val defaultConfig=JVMGopherConfig( diff --git a/shared/src/main/scala/gopher/Channel.scala b/shared/src/main/scala/gopher/Channel.scala index 817765e2..94b20e8e 100644 --- a/shared/src/main/scala/gopher/Channel.scala +++ b/shared/src/main/scala/gopher/Channel.scala @@ -6,6 +6,11 @@ import scala.concurrent.duration.FiniteDuration import gopher.impl._ +/** + * Channel with ability to read and to write. + * @see [[gopher.ReadChannel]] + * @see [[gopher.WriteChannel]] + **/ trait Channel[F[_],W,R] extends WriteChannel[F,W] with ReadChannel[F,R] with Closeable: override def gopherApi: Gopher[F] diff --git a/shared/src/main/scala/gopher/Gopher.scala b/shared/src/main/scala/gopher/Gopher.scala index ef12067f..de7891c4 100644 --- a/shared/src/main/scala/gopher/Gopher.scala +++ b/shared/src/main/scala/gopher/Gopher.scala @@ -16,8 +16,8 @@ import java.util.concurrent.Executor * Process here - scala units of execution (i.e. functions, blok of code, etc). * Communication channels represented by [gopher.Channel] * - * @see [gopher.Channel] - * @see [gopher#select] + * @see [[gopher.Channel]] + * @see [[gopher#select]] **/ trait Gopher[F[_]:CpsSchedulingMonad]: @@ -48,13 +48,14 @@ trait Gopher[F[_]:CpsSchedulingMonad]: /*** *Create a select statement, which used for choosing one action from a set of potentially concurrent asynchronics events. - *[@see gopher.Select] + *[@see [[gopher.Select#apply]] **/ def select: Select[F] = new Select[F](this) /** * get an object with time operations. + * @see [[gopher.Time]] **/ def time: Time[F] From 36c9541ecddd02eb52c027ff5c33750e8ebde473 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Mon, 18 Oct 2021 21:41:29 +0300 Subject: [PATCH 114/161] addopted to dotty 3.1.0, dotty-cps-async 0.9.4 --- build.sbt | 4 ++-- .../gopher/impl/GuardedSPSCBufferedChannel.scala | 4 ++-- project/plugins.sbt | 4 ++-- shared/src/main/scala/gopher/SelectMacro.scala | 13 ++++++++++--- 4 files changed, 16 insertions(+), 9 deletions(-) diff --git a/build.sbt b/build.sbt index 9716523b..ebbfa750 100644 --- a/build.sbt +++ b/build.sbt @@ -1,5 +1,5 @@ //val dottyVersion = "3.0.0-RC2-bin-SNAPSHOT" -val dottyVersion = "3.1.0-RC2" +val dottyVersion = "3.1.0" //val dottyVersion = dottyLatestNightlyBuild.get ThisBuild/version := "2.1.1-SNAPSHOT" @@ -10,7 +10,7 @@ val sharedSettings = Seq( scalaVersion := dottyVersion, name := "scala-gopher", resolvers += "Local Ivy Repository" at "file://"+Path.userHome.absolutePath+"/.ivy2/local", - libraryDependencies += "com.github.rssh" %%% "dotty-cps-async" % "0.9.3", + libraryDependencies += "com.github.rssh" %%% "dotty-cps-async" % "0.9.4", libraryDependencies += "org.scalameta" %%% "munit" % "0.7.27" % Test, ) diff --git a/jvm/src/main/scala/gopher/impl/GuardedSPSCBufferedChannel.scala b/jvm/src/main/scala/gopher/impl/GuardedSPSCBufferedChannel.scala index 96b2d176..bc4d94c7 100644 --- a/jvm/src/main/scala/gopher/impl/GuardedSPSCBufferedChannel.scala +++ b/jvm/src/main/scala/gopher/impl/GuardedSPSCBufferedChannel.scala @@ -1,7 +1,7 @@ package gopher.impl -import cps._ -import gopher._ +import cps.* +import gopher.* import java.util.concurrent.ExecutorService import java.util.concurrent.atomic.AtomicReferenceArray import java.util.concurrent.atomic.AtomicInteger diff --git a/project/plugins.sbt b/project/plugins.sbt index e0e123cc..ef25e945 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -2,5 +2,5 @@ addSbtPlugin("com.jsuereth" % "sbt-pgp" % "2.0.2") addSbtPlugin("com.typesafe.sbt" % "sbt-site" % "1.4.1") addSbtPlugin("com.typesafe.sbt" % "sbt-ghpages" % "0.6.3") addSbtPlugin("org.portable-scala" % "sbt-scalajs-crossproject" % "1.0.0") -addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.7.0") -addSbtPlugin("com.typesafe" % "sbt-mima-plugin" % "1.0.0") +addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.7.1") +addSbtPlugin("com.typesafe" % "sbt-mima-plugin" % "1.0.1") diff --git a/shared/src/main/scala/gopher/SelectMacro.scala b/shared/src/main/scala/gopher/SelectMacro.scala index 9fd6b10b..4ed5ec4a 100644 --- a/shared/src/main/scala/gopher/SelectMacro.scala +++ b/shared/src/main/scala/gopher/SelectMacro.scala @@ -6,6 +6,7 @@ import cps._ import scala.quoted._ import scala.compiletime._ import scala.concurrent.duration._ +import scala.util.control.NonFatal @@ -194,7 +195,13 @@ object SelectMacro: else reportError("Incorrect select pattern, expected or x:channel.{read,write} or Channel.{Read,Write}",chObj.asExpr) - + def safeShow(t:Tree): String = + try + t.show + catch + case NonFatal(ex) => + ex.printStackTrace() + s"(exception durign show:${ex.getMessage()})" caseDef.pattern match case Inlined(_,List(),body) => @@ -232,7 +239,7 @@ object SelectMacro: "unapply"),targs), impl,List(b@Bind(e,ePat),Bind(ch,chPat))) => handleUnapply(chObj, nameReadOrWrite, b, e, ePat, ch) - case pat@Typed(Unapply(TypeApply(quotes.reflect.Select( + case pat@TypedOrTest(Unapply(TypeApply(quotes.reflect.Select( quotes.reflect.Select(chobj,nameReadOrWrite), "unapply"),targs), impl,List(b@Bind(e,ePat),Bind(ch,chPat))),a) => @@ -245,7 +252,7 @@ object SelectMacro: v: channel.write if v == expr v: Time.after if v == expr we have - ${caseDef.pattern.show} + ${safeShow(caseDef.pattern)} (tree: ${caseDef.pattern}) """, caseDef.pattern.asExpr) reportError(s"unparsed caseDef pattern: ${caseDef.pattern}", caseDef.pattern.asExpr) From 42ea86d762348c6b2e51cae7b722f55aa59f8d60 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Mon, 18 Oct 2021 21:50:00 +0300 Subject: [PATCH 115/161] restored binary compability with 2.1.0 --- build.sbt | 2 +- jvm/src/main/scala/gopher/JVMGopher.scala | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index ebbfa750..d146f656 100644 --- a/build.sbt +++ b/build.sbt @@ -2,7 +2,7 @@ val dottyVersion = "3.1.0" //val dottyVersion = dottyLatestNightlyBuild.get -ThisBuild/version := "2.1.1-SNAPSHOT" +ThisBuild/version := "2.1.1" ThisBuild/versionScheme := Some("semver-spec") val sharedSettings = Seq( diff --git a/jvm/src/main/scala/gopher/JVMGopher.scala b/jvm/src/main/scala/gopher/JVMGopher.scala index 4598eeb0..178bfb77 100644 --- a/jvm/src/main/scala/gopher/JVMGopher.scala +++ b/jvm/src/main/scala/gopher/JVMGopher.scala @@ -61,6 +61,10 @@ object JVMGopher extends GopherAPI: taskExecutor=ForkJoinPool.commonPool(), ) + // need for binary compability + @deprecated("use summon[Gopher].time instead") + lazy val timer = new Timer("gopher") + val logger = Logger.getLogger("JVMGopher") def defaultLogFun(level: Level, message:String, ex: Throwable|Null): Unit = From 909906f5c1ea48237a1d4ce1381f6b7a99952ce2 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Tue, 19 Oct 2021 07:29:49 +0300 Subject: [PATCH 116/161] 2.1.2-SNAPSHOT --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index d146f656..75e8eddf 100644 --- a/build.sbt +++ b/build.sbt @@ -2,7 +2,7 @@ val dottyVersion = "3.1.0" //val dottyVersion = dottyLatestNightlyBuild.get -ThisBuild/version := "2.1.1" +ThisBuild/version := "2.1.2-SNAPSHOT" ThisBuild/versionScheme := Some("semver-spec") val sharedSettings = Seq( From 6245794a0139e0fe5323681d0ef45f9ccb6f8abf Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Sat, 11 Dec 2021 08:03:17 +0200 Subject: [PATCH 117/161] sbt-1.5.6, munit-0.7.29 --- build.sbt | 2 +- project/build.properties | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build.sbt b/build.sbt index 75e8eddf..3599856d 100644 --- a/build.sbt +++ b/build.sbt @@ -11,7 +11,7 @@ val sharedSettings = Seq( name := "scala-gopher", resolvers += "Local Ivy Repository" at "file://"+Path.userHome.absolutePath+"/.ivy2/local", libraryDependencies += "com.github.rssh" %%% "dotty-cps-async" % "0.9.4", - libraryDependencies += "org.scalameta" %%% "munit" % "0.7.27" % Test, + libraryDependencies += "org.scalameta" %%% "munit" % "0.7.29" % Test, ) lazy val root = project diff --git a/project/build.properties b/project/build.properties index 10fd9eee..bb3a9b7d 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=1.5.5 +sbt.version=1.5.6 From 107ffea0083bbf1df2d712ce3d92e34ee37b9a46 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Mon, 24 Jan 2022 17:50:56 +0200 Subject: [PATCH 118/161] adopted do dotty-cps-async-0.9.6 --- README.md | 8 ++++++- build.sbt | 8 +++---- project/plugins.sbt | 2 +- shared/src/main/scala/gopher/Gopher.scala | 2 +- .../src/main/scala/gopher/ReadChannel.scala | 24 +++++++++++-------- shared/src/main/scala/gopher/Select.scala | 7 +++--- .../src/main/scala/gopher/SelectForever.scala | 4 ++-- .../src/main/scala/gopher/SelectGroup.scala | 8 +++---- .../main/scala/gopher/SelectListeners.scala | 2 +- shared/src/main/scala/gopher/SelectLoop.scala | 4 ++-- .../src/main/scala/gopher/SelectMacro.scala | 18 +++++++------- shared/src/main/scala/gopher/Time.scala | 4 ++-- .../src/main/scala/gopher/WriteChannel.scala | 10 ++++---- .../gopher/monads/ReadChannelCpsMonad.scala | 3 ++- .../monads/ReadTryChannelCpsMonad.scala | 2 +- .../src/test/scala/examples/SieveSuite.scala | 1 + 16 files changed, 61 insertions(+), 46 deletions(-) diff --git a/README.md b/README.md index c1921f77..92ea923e 100644 --- a/README.md +++ b/README.md @@ -4,10 +4,16 @@ ### Dependences: -For scala 3: +For scala 3.1.1+: + + libraryDependencies += "com.github.rssh" %% "scala-gopher" % "3.0.0" + +For scala 3 and 3.1.0: libraryDependencies += "com.github.rssh" %% "scala-gopher" % "2.1.0" +Note, that 3.0.0 have no new functionality agains 2.1.0 but need to be a next major release because of binary incompability caused by difference between dotty-cps-async-0.9.5 and 0.9.6. + For scala2: libraryDependencies += "com.github.rssh" %% "scala-gopher" % "0.99.15" diff --git a/build.sbt b/build.sbt index 3599856d..62bfee0d 100644 --- a/build.sbt +++ b/build.sbt @@ -1,8 +1,8 @@ //val dottyVersion = "3.0.0-RC2-bin-SNAPSHOT" -val dottyVersion = "3.1.0" +val dottyVersion = "3.1.1" //val dottyVersion = dottyLatestNightlyBuild.get -ThisBuild/version := "2.1.2-SNAPSHOT" +ThisBuild/version := "3.0.0" ThisBuild/versionScheme := Some("semver-spec") val sharedSettings = Seq( @@ -10,7 +10,7 @@ val sharedSettings = Seq( scalaVersion := dottyVersion, name := "scala-gopher", resolvers += "Local Ivy Repository" at "file://"+Path.userHome.absolutePath+"/.ivy2/local", - libraryDependencies += "com.github.rssh" %%% "dotty-cps-async" % "0.9.4", + libraryDependencies += "com.github.rssh" %%% "dotty-cps-async" % "0.9.6", libraryDependencies += "org.scalameta" %%% "munit" % "0.7.29" % Test, ) @@ -45,7 +45,7 @@ lazy val gopher = crossProject(JSPlatform, JVMPlatform) Compile / doc / scalacOptions := Seq("-groups", "-source-links:shared=github://rssh/scala-gopher/master#shared", "-source-links:jvm=github://rssh/scala-gopher/master#jvm"), - mimaPreviousArtifacts := Set( "com.github.rssh" %% "scala-gopher" % "2.1.0") + mimaPreviousArtifacts := Set() //Set( "com.github.rssh" %% "scala-gopher" % "2.1.0") ).jsSettings( libraryDependencies += ("org.scala-js" %%% "scalajs-java-logging" % "1.0.0").cross(CrossVersion.for3Use2_13), // TODO: switch to ModuleES ? diff --git a/project/plugins.sbt b/project/plugins.sbt index ef25e945..0b41d0fd 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -2,5 +2,5 @@ addSbtPlugin("com.jsuereth" % "sbt-pgp" % "2.0.2") addSbtPlugin("com.typesafe.sbt" % "sbt-site" % "1.4.1") addSbtPlugin("com.typesafe.sbt" % "sbt-ghpages" % "0.6.3") addSbtPlugin("org.portable-scala" % "sbt-scalajs-crossproject" % "1.0.0") -addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.7.1") +addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.8.0") addSbtPlugin("com.typesafe" % "sbt-mima-plugin" % "1.0.1") diff --git a/shared/src/main/scala/gopher/Gopher.scala b/shared/src/main/scala/gopher/Gopher.scala index de7891c4..cfe52c26 100644 --- a/shared/src/main/scala/gopher/Gopher.scala +++ b/shared/src/main/scala/gopher/Gopher.scala @@ -83,7 +83,7 @@ trait Gopher[F[_]:CpsSchedulingMonad]: } end Gopher - + /** * Create Read/Write channel. diff --git a/shared/src/main/scala/gopher/ReadChannel.scala b/shared/src/main/scala/gopher/ReadChannel.scala index 53099e88..a20ef958 100644 --- a/shared/src/main/scala/gopher/ReadChannel.scala +++ b/shared/src/main/scala/gopher/ReadChannel.scala @@ -52,12 +52,12 @@ trait ReadChannel[F[_], A]: * Can be used only inside async block. * If stream is closed and no values to read left in the stream - throws StreamClosedException **/ - transparent inline def read(): A = await(aread())(using rAsyncMonad) + transparent inline def read()(using CpsMonadContext[F]): A = await(aread())(using rAsyncMonad) /** * Synonim for read. */ - transparent inline def ? : A = await(aread())(using rAsyncMonad) + transparent inline def ?(using CpsMonadContext[F]) : A = await(aread())(using rAsyncMonad) /** * return F which contains sequence from first `n` elements. @@ -83,7 +83,7 @@ trait ReadChannel[F[_], A]: * take first `n` elements. * should be called inside async block. **/ - transparent inline def take(n: Int): IndexedSeq[A] = + transparent inline def take(n: Int)(using CpsMonadContext[F]): IndexedSeq[A] = await(atake(n))(using rAsyncMonad) /** @@ -107,7 +107,7 @@ trait ReadChannel[F[_], A]: * * should be called inside async block. **/ - transparent inline def optRead(): Option[A] = await(aOptRead())(using rAsyncMonad) + transparent inline def optRead()(using CpsMonadContext[F]): Option[A] = await(aOptRead())(using rAsyncMonad) def foreach_async(f: A=>F[Unit]): F[Unit] = given CpsAsyncMonad[F] = asyncMonad @@ -130,7 +130,7 @@ trait ReadChannel[F[_], A]: * run code each time when new object is arriced. * until end of stream is not reached **/ - transparent inline def foreach(inline f: A=>Unit): Unit = + transparent inline def foreach(inline f: A=>Unit)(using CpsMonadContext[F]): Unit = await(aforeach(f))(using rAsyncMonad) @@ -170,8 +170,8 @@ trait ReadChannel[F[_], A]: s } - transparent inline def fold[S](inline s0:S)(inline f: (S,A) => S ): S = - await[F,S](afold(s0)(f))(using rAsyncMonad) + transparent inline def fold[S](inline s0:S)(inline f: (S,A) => S )(using mc:CpsMonadContext[F]): S = + await[F,S,F](afold(s0)(f))(using rAsyncMonad, mc) def zip[B](x: ReadChannel[F,B]): ReadChannel[F,(A,B)] = given CpsSchedulingMonad[F] = asyncMonad @@ -317,14 +317,18 @@ object ReadChannel: } - + import cps.stream._ - given emitAbsorber[F[_]: CpsSchedulingMonad,T](using gopherApi: Gopher[F]): BaseUnfoldCpsAsyncEmitAbsorber[ReadChannel[F,T],F,T]( - using gopherApi.asyncMonad, gopherApi.taskExecutionContext) with + given emitAbsorber[F[_], C<:CpsMonadContext[F], T](using auxMonad: CpsSchedulingMonad[F]{ type Context = C }, gopherApi: Gopher[F]): BaseUnfoldCpsAsyncEmitAbsorber[ReadChannel[F,T],F,C,T]( + using gopherApi.taskExecutionContext, auxMonad) with override type Element = T + def asSync(fs: F[ReadChannel[F,T]]): ReadChannel[F,T] = + DelayedReadChannel(fs) + + def unfold[S](s0:S)(f: S => F[Option[(T,S)]]): ReadChannel[F,T] = val r: ReadChannel[F,T] = unfoldAsync(s0)(f) r diff --git a/shared/src/main/scala/gopher/Select.scala b/shared/src/main/scala/gopher/Select.scala index e4984087..fb3d93fe 100644 --- a/shared/src/main/scala/gopher/Select.scala +++ b/shared/src/main/scala/gopher/Select.scala @@ -26,9 +26,9 @@ class Select[F[_]](api: Gopher[F]): *} *``` */ - transparent inline def apply[A](inline pf: PartialFunction[Any,A]): A = + transparent inline def apply[A](inline pf: PartialFunction[Any,A])(using mc:CpsMonadContext[F]): A = ${ - SelectMacro.onceImpl[F,A]('pf, 'api ) + SelectMacro.onceImpl[F,A]('pf, 'api, 'mc ) } /** @@ -68,7 +68,8 @@ class Select[F[_]](api: Gopher[F]): } transparent inline def afold[S](s0:S)(inline step: S => S | SelectFold.Done[S]) : F[S] = - async[F](using api.asyncMonad).apply{ + given CpsAsyncMonad[F] = api.asyncMonad + async[F]{ fold(s0)(step) } diff --git a/shared/src/main/scala/gopher/SelectForever.scala b/shared/src/main/scala/gopher/SelectForever.scala index 8f1b2352..3fecc3a9 100644 --- a/shared/src/main/scala/gopher/SelectForever.scala +++ b/shared/src/main/scala/gopher/SelectForever.scala @@ -12,9 +12,9 @@ import scala.concurrent.duration._ class SelectForever[F[_]](api: Gopher[F]) extends SelectGroupBuilder[F,Unit, Unit](api): - transparent inline def apply(inline pf: PartialFunction[Any,Unit]): Unit = + transparent inline def apply(inline pf: PartialFunction[Any,Unit])(using mc:CpsMonadContext[F]): Unit = ${ - SelectMacro.foreverImpl('pf,'api) + SelectMacro.foreverImpl('pf,'api, 'mc) } diff --git a/shared/src/main/scala/gopher/SelectGroup.scala b/shared/src/main/scala/gopher/SelectGroup.scala index 9f3b7306..6db7cd2c 100644 --- a/shared/src/main/scala/gopher/SelectGroup.scala +++ b/shared/src/main/scala/gopher/SelectGroup.scala @@ -73,14 +73,14 @@ class SelectGroup[F[_], S](api: Gopher[F]) extends SelectListeners[F,S,S]: def runAsync():F[S] = retval - transparent inline def apply(inline pf: PartialFunction[Any,S]): S = + transparent inline def apply(inline pf: PartialFunction[Any,S])(using mc: CpsMonadContext[F]): S = ${ - SelectMacro.onceImpl[F,S]('pf, 'api ) + SelectMacro.onceImpl[F,S]('pf, 'api, 'mc ) } - transparent inline def select(inline pf: PartialFunction[Any,S]): S = + transparent inline def select(inline pf: PartialFunction[Any,S])(using mc: CpsMonadContext[F]): S = ${ - SelectMacro.onceImpl[F,S]('pf, 'api ) + SelectMacro.onceImpl[F,S]('pf, 'api, 'mc ) } /** diff --git a/shared/src/main/scala/gopher/SelectListeners.scala b/shared/src/main/scala/gopher/SelectListeners.scala index ae1cde64..14440418 100644 --- a/shared/src/main/scala/gopher/SelectListeners.scala +++ b/shared/src/main/scala/gopher/SelectListeners.scala @@ -16,7 +16,7 @@ trait SelectListeners[F[_],S, R]: def runAsync():F[R] - transparent inline def run(): R = await(runAsync())(using asyncMonad) + transparent inline def run()(using CpsMonadContext[F]): R = await(runAsync())(using asyncMonad) diff --git a/shared/src/main/scala/gopher/SelectLoop.scala b/shared/src/main/scala/gopher/SelectLoop.scala index c02a1f3c..7393a2b1 100644 --- a/shared/src/main/scala/gopher/SelectLoop.scala +++ b/shared/src/main/scala/gopher/SelectLoop.scala @@ -11,9 +11,9 @@ import java.util.logging.{Level => LogLevel} class SelectLoop[F[_]](api: Gopher[F]) extends SelectGroupBuilder[F,Boolean, Unit](api): - transparent inline def apply(inline pf: PartialFunction[Any,Boolean]): Unit = + transparent inline def apply(inline pf: PartialFunction[Any,Boolean])(using mc: CpsMonadContext[F]): Unit = ${ - SelectMacro.loopImpl[F]('pf, 'api ) + SelectMacro.loopImpl[F]('pf, 'api, 'mc ) } def runAsync(): F[Unit] = diff --git a/shared/src/main/scala/gopher/SelectMacro.scala b/shared/src/main/scala/gopher/SelectMacro.scala index 4ed5ec4a..23639188 100644 --- a/shared/src/main/scala/gopher/SelectMacro.scala +++ b/shared/src/main/scala/gopher/SelectMacro.scala @@ -51,10 +51,12 @@ object SelectMacro: def buildSelectListenerRun[F[_]:Type, S:Type, R:Type, L <: SelectListeners[F,S,R]:Type]( constructor: Expr[L], caseDefs: List[SelectorCaseExpr[F,S,R]], - api:Expr[Gopher[F]])(using Quotes): Expr[R] = + api:Expr[Gopher[F]], + monadContext: Expr[CpsMonadContext[F]], + )(using Quotes): Expr[R] = val g = selectListenerBuilder(constructor, caseDefs) // dotty bug if g.run - val r = '{ await($g.runAsync())(using ${api}.asyncMonad) } + val r = '{ await($g.runAsync())(using ${api}.asyncMonad, $monadContext) } r.asExprOf[R] def buildSelectListenerRunAsync[F[_]:Type, S:Type, R:Type, L <: SelectListeners[F,S,R]:Type]( @@ -68,31 +70,31 @@ object SelectMacro: - def onceImpl[F[_]:Type, A:Type](pf: Expr[PartialFunction[Any,A]], api: Expr[Gopher[F]])(using Quotes): Expr[A] = + def onceImpl[F[_]:Type, A:Type](pf: Expr[PartialFunction[Any,A]], api: Expr[Gopher[F]], monadContext: Expr[CpsMonadContext[F]])(using Quotes): Expr[A] = def builder(caseDefs: List[SelectorCaseExpr[F,A,A]]):Expr[A] = { val s0 = '{ new SelectGroup[F,A]($api) } - buildSelectListenerRun(s0, caseDefs, api) + buildSelectListenerRun(s0, caseDefs, api, monadContext) } runImpl(builder, pf) - def loopImpl[F[_]:Type](pf: Expr[PartialFunction[Any,Boolean]], api: Expr[Gopher[F]])(using Quotes): Expr[Unit] = + def loopImpl[F[_]:Type](pf: Expr[PartialFunction[Any,Boolean]], api: Expr[Gopher[F]], monadContext: Expr[CpsMonadContext[F]])(using Quotes): Expr[Unit] = def builder(caseDefs: List[SelectorCaseExpr[F,Boolean,Unit]]):Expr[Unit] = { val s0 = '{ new SelectLoop[F]($api) } - buildSelectListenerRun(s0, caseDefs, api) + buildSelectListenerRun(s0, caseDefs, api, monadContext) } runImpl( builder, pf) - def foreverImpl[F[_]:Type](pf: Expr[PartialFunction[Any,Unit]], api:Expr[Gopher[F]])(using Quotes): Expr[Unit] = + def foreverImpl[F[_]:Type](pf: Expr[PartialFunction[Any,Unit]], api:Expr[Gopher[F]], monadContext: Expr[CpsMonadContext[F]])(using Quotes): Expr[Unit] = def builder(caseDefs: List[SelectorCaseExpr[F,Unit,Unit]]):Expr[Unit] = { val s0 = '{ new SelectForever[F]($api) } - buildSelectListenerRun(s0, caseDefs, api) + buildSelectListenerRun(s0, caseDefs, api, monadContext) } runImpl(builder, pf) diff --git a/shared/src/main/scala/gopher/Time.scala b/shared/src/main/scala/gopher/Time.scala index 010af07f..ec4f1309 100644 --- a/shared/src/main/scala/gopher/Time.scala +++ b/shared/src/main/scala/gopher/Time.scala @@ -60,7 +60,7 @@ abstract class Time[F[_]](gopherAPI: Gopher[F]) { /** * synonim for `await(asleep(duration))`. Should be used inside async block. **/ - transparent inline def sleep(duration: FiniteDuration): FiniteDuration = + transparent inline def sleep(duration: FiniteDuration)(using CpsMonadContext[F]): FiniteDuration = given CpsSchedulingMonad[F] = gopherAPI.asyncMonad await(asleep(duration)) @@ -156,7 +156,7 @@ object Time: def asleep[F[_]](duration: FiniteDuration)(using Gopher[F]): F[FiniteDuration] = summon[Gopher[F]].time.asleep(duration) - transparent inline def sleep[F[_]](duration: FiniteDuration)(using Gopher[F]): FiniteDuration = + transparent inline def sleep[F[_]](duration: FiniteDuration)(using Gopher[F], CpsMonadContext[F]): FiniteDuration = summon[Gopher[F]].time.sleep(duration) diff --git a/shared/src/main/scala/gopher/WriteChannel.scala b/shared/src/main/scala/gopher/WriteChannel.scala index a0e25cd6..758455f8 100644 --- a/shared/src/main/scala/gopher/WriteChannel.scala +++ b/shared/src/main/scala/gopher/WriteChannel.scala @@ -23,13 +23,13 @@ trait WriteChannel[F[_], A]: // inline def apply(a:A): Unit = await(awrite(a))(using asyncMonad) // inline def unapply(a:A): Some[A] = ??? - transparent inline def write(inline a:A): Unit = await(awrite(a))(using asyncMonad) + transparent inline def write(inline a:A)(using CpsMonadContext[F]): Unit = await(awrite(a))(using asyncMonad) @targetName("write1") - transparent inline def <~ (inline a:A): Unit = await(awrite(a))(using asyncMonad) + transparent inline def <~ (inline a:A)(using CpsMonadContext[F]): Unit = await(awrite(a))(using asyncMonad) @targetName("write2") - transparent inline def ! (inline a:A): Unit = await(awrite(a))(using asyncMonad) + transparent inline def ! (inline a:A)(using CpsMonadContext[F]): Unit = await(awrite(a))(using asyncMonad) //def Write(x:A):WritePattern = new WritePattern(x) @@ -51,8 +51,8 @@ trait WriteChannel[F[_], A]: } } - transparent inline def writeAll(inline collection: IterableOnce[A]): Unit = - await(awriteAll(collection))(using asyncMonad) + transparent inline def writeAll(inline collection: IterableOnce[A])(using mc: CpsMonadContext[F]): Unit = + await(awriteAll(collection))(using asyncMonad, mc) def withWriteExpiration(ttl: FiniteDuration, throwTimeouts: Boolean)(using gopherApi: Gopher[F]): WriteChannelWithExpiration[F,A] = diff --git a/shared/src/main/scala/gopher/monads/ReadChannelCpsMonad.scala b/shared/src/main/scala/gopher/monads/ReadChannelCpsMonad.scala index f1a35596..a29d9501 100644 --- a/shared/src/main/scala/gopher/monads/ReadChannelCpsMonad.scala +++ b/shared/src/main/scala/gopher/monads/ReadChannelCpsMonad.scala @@ -6,7 +6,8 @@ import cps._ import gopher.impl._ -given ReadChannelCpsMonad[F[_]](using Gopher[F]): CpsMonad[ [A] =>> ReadChannel[F,A]] with +given ReadChannelCpsMonad[F[_]](using Gopher[F]): CpsMonad[[A] =>> ReadChannel[F,A]] with CpsMonadInstanceContext[[A] =>> ReadChannel[F,A]] with + def pure[T](t:T): ReadChannel[F,T] = ReadChannel.fromValues[F,T](t) diff --git a/shared/src/main/scala/gopher/monads/ReadTryChannelCpsMonad.scala b/shared/src/main/scala/gopher/monads/ReadTryChannelCpsMonad.scala index 2516f735..02fafe69 100644 --- a/shared/src/main/scala/gopher/monads/ReadTryChannelCpsMonad.scala +++ b/shared/src/main/scala/gopher/monads/ReadTryChannelCpsMonad.scala @@ -7,7 +7,7 @@ import cps._ import gopher.impl._ -given ReadTryChannelCpsMonad[F[_]](using Gopher[F]): CpsAsyncMonad[ [A] =>> ReadChannel[F,Try[A]] ] with +given ReadTryChannelCpsMonad[F[_]](using Gopher[F]): CpsAsyncMonad[[A] =>> ReadChannel[F,Try[A]]] with CpsMonadInstanceContext[[A] =>> ReadChannel[F,Try[A]]] with type FW[T] = [A] =>> ReadChannel[F,Try[A]] diff --git a/shared/src/test/scala/examples/SieveSuite.scala b/shared/src/test/scala/examples/SieveSuite.scala index 9ea58d02..a8423bda 100644 --- a/shared/src/test/scala/examples/SieveSuite.scala +++ b/shared/src/test/scala/examples/SieveSuite.scala @@ -48,6 +48,7 @@ object Sieve def filter1(in:Channel[Future,Int,Int]):ReadChannel[Future,Int] = { + //implicit val printCode = cps.macros.flags.PrintCode val q = makeChannel[Int]() val filtered = makeChannel[Int]() select.afold(in){ ch => From 51e112fe1de6cd8596703dac2fa4377873d9ff81 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Mon, 24 Jan 2022 17:58:41 +0200 Subject: [PATCH 119/161] 3.0.1-SNAPSHOT --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index 62bfee0d..d350b623 100644 --- a/build.sbt +++ b/build.sbt @@ -2,7 +2,7 @@ val dottyVersion = "3.1.1" //val dottyVersion = dottyLatestNightlyBuild.get -ThisBuild/version := "3.0.0" +ThisBuild/version := "3.0.1-SNAPSHOT" ThisBuild/versionScheme := Some("semver-spec") val sharedSettings = Seq( From de564ae34d51763dabf037b1b1bea6a66c4b2d20 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Tue, 25 Jan 2022 12:14:56 +0200 Subject: [PATCH 120/161] prepare for 3.0.1 release with changed dependency --- README.md | 4 ++-- build.sbt | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 92ea923e..c47f2d95 100644 --- a/README.md +++ b/README.md @@ -6,13 +6,13 @@ For scala 3.1.1+: - libraryDependencies += "com.github.rssh" %% "scala-gopher" % "3.0.0" + libraryDependencies += "com.github.rssh" %% "scala-gopher" % "3.0.1" For scala 3 and 3.1.0: libraryDependencies += "com.github.rssh" %% "scala-gopher" % "2.1.0" -Note, that 3.0.0 have no new functionality agains 2.1.0 but need to be a next major release because of binary incompability caused by difference between dotty-cps-async-0.9.5 and 0.9.6. +Note, that 3.0.x have no new functionality agains 2.1.0 but need to be a next major release because of binary incompability caused by difference between dotty-cps-async-0.9.5 and 0.9.7. For scala2: diff --git a/build.sbt b/build.sbt index d350b623..6a268359 100644 --- a/build.sbt +++ b/build.sbt @@ -2,7 +2,7 @@ val dottyVersion = "3.1.1" //val dottyVersion = dottyLatestNightlyBuild.get -ThisBuild/version := "3.0.1-SNAPSHOT" +ThisBuild/version := "3.0.1" ThisBuild/versionScheme := Some("semver-spec") val sharedSettings = Seq( @@ -10,7 +10,7 @@ val sharedSettings = Seq( scalaVersion := dottyVersion, name := "scala-gopher", resolvers += "Local Ivy Repository" at "file://"+Path.userHome.absolutePath+"/.ivy2/local", - libraryDependencies += "com.github.rssh" %%% "dotty-cps-async" % "0.9.6", + libraryDependencies += "com.github.rssh" %%% "dotty-cps-async" % "0.9.7", libraryDependencies += "org.scalameta" %%% "munit" % "0.7.29" % Test, ) @@ -45,7 +45,7 @@ lazy val gopher = crossProject(JSPlatform, JVMPlatform) Compile / doc / scalacOptions := Seq("-groups", "-source-links:shared=github://rssh/scala-gopher/master#shared", "-source-links:jvm=github://rssh/scala-gopher/master#jvm"), - mimaPreviousArtifacts := Set() //Set( "com.github.rssh" %% "scala-gopher" % "2.1.0") + mimaPreviousArtifacts := Set( "com.github.rssh" %% "scala-gopher" % "3.0.0" ) ).jsSettings( libraryDependencies += ("org.scala-js" %%% "scalajs-java-logging" % "1.0.0").cross(CrossVersion.for3Use2_13), // TODO: switch to ModuleES ? From 5d2ee29631b08bd06f2732cc70ae8564dca53e23 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Tue, 25 Jan 2022 16:17:26 +0200 Subject: [PATCH 121/161] sbt-1.6.1 --- project/build.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/build.properties b/project/build.properties index bb3a9b7d..3161d214 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=1.5.6 +sbt.version=1.6.1 From 17bfcff97c3ad3eb308ee3b4a93a61b666b74f01 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Tue, 25 Jan 2022 16:18:07 +0200 Subject: [PATCH 122/161] 3.0.2-SNAPSHOT --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index 6a268359..3558a38f 100644 --- a/build.sbt +++ b/build.sbt @@ -2,7 +2,7 @@ val dottyVersion = "3.1.1" //val dottyVersion = dottyLatestNightlyBuild.get -ThisBuild/version := "3.0.1" +ThisBuild/version := "3.0.2-SNAPSHOT" ThisBuild/versionScheme := Some("semver-spec") val sharedSettings = Seq( From 13f31286d9b389b0bf91ff34eecafc3ce41423a6 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Sun, 13 Feb 2022 14:25:54 +0200 Subject: [PATCH 123/161] adopted to dotty-cps-async 0.9.8 --- README.md | 2 +- build.sbt | 9 +++++---- .../src/main/scala/gopher/ReadChannel.scala | 4 ++-- shared/src/main/scala/gopher/Select.scala | 2 +- .../gopher/monads/ReadChannelCpsMonad.scala | 3 ++- .../gopher/monads/ChannelMonadSuite.scala | 19 ++++++++++++------- 6 files changed, 23 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index c47f2d95..d3bc49bd 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ For scala 3.1.1+: - libraryDependencies += "com.github.rssh" %% "scala-gopher" % "3.0.1" + libraryDependencies += "com.github.rssh" %% "scala-gopher" % "3.0.2" For scala 3 and 3.1.0: diff --git a/build.sbt b/build.sbt index 3558a38f..f42aa593 100644 --- a/build.sbt +++ b/build.sbt @@ -1,5 +1,6 @@ //val dottyVersion = "3.0.0-RC2-bin-SNAPSHOT" val dottyVersion = "3.1.1" +//val dottyVersion = "3.1.3-RC1-bin-SNAPSHOT" //val dottyVersion = dottyLatestNightlyBuild.get ThisBuild/version := "3.0.2-SNAPSHOT" @@ -10,7 +11,7 @@ val sharedSettings = Seq( scalaVersion := dottyVersion, name := "scala-gopher", resolvers += "Local Ivy Repository" at "file://"+Path.userHome.absolutePath+"/.ivy2/local", - libraryDependencies += "com.github.rssh" %%% "dotty-cps-async" % "0.9.7", + libraryDependencies += "com.github.rssh" %%% "dotty-cps-async" % "0.9.8", libraryDependencies += "org.scalameta" %%% "munit" % "0.7.29" % Test, ) @@ -26,14 +27,14 @@ lazy val root = project ).enablePlugins(GhpagesPlugin, SiteScaladocPlugin) - +// for scala-native support we need munit lazy val gopher = crossProject(JSPlatform, JVMPlatform) .in(file(".")) .settings(sharedSettings) .disablePlugins(SitePlugin) .disablePlugins(SitePreviewPlugin) .jvmSettings( - scalacOptions ++= Seq( "-unchecked", "-Ycheck:macros", "-uniqid", "-Xprint:types" ), + scalacOptions ++= Seq( "-unchecked", "-Ycheck:macros", "-uniqid", "-Xprint:types", "-explain" ), fork := true, /* javaOptions ++= Seq( @@ -45,7 +46,7 @@ lazy val gopher = crossProject(JSPlatform, JVMPlatform) Compile / doc / scalacOptions := Seq("-groups", "-source-links:shared=github://rssh/scala-gopher/master#shared", "-source-links:jvm=github://rssh/scala-gopher/master#jvm"), - mimaPreviousArtifacts := Set( "com.github.rssh" %% "scala-gopher" % "3.0.0" ) + mimaPreviousArtifacts := Set( "com.github.rssh" %% "scala-gopher" % "3.0.1" ) ).jsSettings( libraryDependencies += ("org.scala-js" %%% "scalajs-java-logging" % "1.0.0").cross(CrossVersion.for3Use2_13), // TODO: switch to ModuleES ? diff --git a/shared/src/main/scala/gopher/ReadChannel.scala b/shared/src/main/scala/gopher/ReadChannel.scala index a20ef958..5b124b7a 100644 --- a/shared/src/main/scala/gopher/ReadChannel.scala +++ b/shared/src/main/scala/gopher/ReadChannel.scala @@ -52,12 +52,12 @@ trait ReadChannel[F[_], A]: * Can be used only inside async block. * If stream is closed and no values to read left in the stream - throws StreamClosedException **/ - transparent inline def read()(using CpsMonadContext[F]): A = await(aread())(using rAsyncMonad) + transparent inline def read[G[_]]()(using mc:CpsMonadContext[G]): A = await(aread())(using rAsyncMonad, mc) /** * Synonim for read. */ - transparent inline def ?(using CpsMonadContext[F]) : A = await(aread())(using rAsyncMonad) + transparent inline def ?(using mc:CpsMonadContext[F]) : A = await(aread())(using rAsyncMonad, mc) /** * return F which contains sequence from first `n` elements. diff --git a/shared/src/main/scala/gopher/Select.scala b/shared/src/main/scala/gopher/Select.scala index fb3d93fe..4ca37b84 100644 --- a/shared/src/main/scala/gopher/Select.scala +++ b/shared/src/main/scala/gopher/Select.scala @@ -67,7 +67,7 @@ class Select[F[_]](api: Gopher[F]): } } - transparent inline def afold[S](s0:S)(inline step: S => S | SelectFold.Done[S]) : F[S] = + transparent inline def afold[S](s0:S)(inline step: CpsMonadContext[F] ?=> S => S | SelectFold.Done[S]) : F[S] = given CpsAsyncMonad[F] = api.asyncMonad async[F]{ fold(s0)(step) diff --git a/shared/src/main/scala/gopher/monads/ReadChannelCpsMonad.scala b/shared/src/main/scala/gopher/monads/ReadChannelCpsMonad.scala index a29d9501..b936ae4f 100644 --- a/shared/src/main/scala/gopher/monads/ReadChannelCpsMonad.scala +++ b/shared/src/main/scala/gopher/monads/ReadChannelCpsMonad.scala @@ -6,7 +6,8 @@ import cps._ import gopher.impl._ -given ReadChannelCpsMonad[F[_]](using Gopher[F]): CpsMonad[[A] =>> ReadChannel[F,A]] with CpsMonadInstanceContext[[A] =>> ReadChannel[F,A]] with + +given ReadChannelCpsMonad[F[_]](using Gopher[F]): CpsMonadInstanceContext[[A] =>> ReadChannel[F,A]] with def pure[T](t:T): ReadChannel[F,T] = diff --git a/shared/src/test/scala/gopher/monads/ChannelMonadSuite.scala b/shared/src/test/scala/gopher/monads/ChannelMonadSuite.scala index 8760ce74..7cfc1338 100644 --- a/shared/src/test/scala/gopher/monads/ChannelMonadSuite.scala +++ b/shared/src/test/scala/gopher/monads/ChannelMonadSuite.scala @@ -8,8 +8,8 @@ import scala.concurrent.* import scala.concurrent.duration.* import scala.collection.SortedSet -import cps.monads.FutureAsyncMonad -import gopher.monads.given +import cps.monads.{given,*} +import gopher.monads.{given,*} class ChannelMonadSuite extends FunSuite { @@ -19,17 +19,21 @@ class ChannelMonadSuite extends FunSuite { test("using channel as monad and read inside") { - val chX = ReadChannel.fromValues(1,2,3,4,5) - val chY = ReadChannel.fromValues(1,2,3,4,5) + + val chX = ReadChannel.fromValues[Future,Int](1,2,3,4,5) + val chY = ReadChannel.fromValues[Future,Int](1,2,3,4,5) - val squares = async[[X] =>> ReadChannel[Future,X]] { - val x = await(chX) + + val squares: ReadChannel[Future,Int] = async[[X] =>> ReadChannel[Future,X]] { + val x: Int = await(chX) //println(s"reading from X $x") - val y = chY.read() + val y: Int = chY.read() //println(s"reading from Y $y") x*y } + + async[Future] { val a1 = squares.read() //println(s"a1==${a1}") @@ -46,6 +50,7 @@ class ChannelMonadSuite extends FunSuite { } } + test("using channel with flatMap") { From 8b14034c5120307f2a85fe6d763868759bfa26d0 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Sun, 13 Feb 2022 14:26:29 +0200 Subject: [PATCH 124/161] sbt-1.6.2 --- project/build.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/build.properties b/project/build.properties index 3161d214..c8fcab54 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=1.6.1 +sbt.version=1.6.2 From 6313814f5b62e1e6a9769747c4206c698dee4007 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Sun, 13 Feb 2022 14:27:10 +0200 Subject: [PATCH 125/161] 3.0.2 release --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index f42aa593..9a8c5614 100644 --- a/build.sbt +++ b/build.sbt @@ -3,7 +3,7 @@ val dottyVersion = "3.1.1" //val dottyVersion = "3.1.3-RC1-bin-SNAPSHOT" //val dottyVersion = dottyLatestNightlyBuild.get -ThisBuild/version := "3.0.2-SNAPSHOT" +ThisBuild/version := "3.0.2" ThisBuild/versionScheme := Some("semver-spec") val sharedSettings = Seq( From 97aa05d174c21f7640894f672660cb24c75edd49 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Thu, 24 Feb 2022 23:06:39 +0200 Subject: [PATCH 126/161] added information message --- README.md | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/README.md b/README.md index d3bc49bd..014801f9 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,28 @@ +# 🇺 🇦 UKRAINE NEEDS YOUR HELP NOW! + + I'm the creator of this project and I'm Ukrainian. +**My country, Ukraine, [is being invaded by the Russian Federation, right now](https://www.bbc.com/news/world-europe-60504334)**. + +**Please, help to save my country!** +Ukrainian National Bank opened [an account to Raise Funds for Ukraine’s Armed Forces](https://bank.gov.ua/en/news/all/natsionalniy-bank-vidkriv-spetsrahunok-dlya-zboru-koshtiv-na-potrebi-armiyi): + + ``` + SWIFT Code NBU: NBUA UA UX + JP MORGAN CHASE BANK, New York + SWIFT Code: CHASUS33 + Account: 400807238 + 383 Madison Avenue, New York, NY 10179, USA + IBAN: UA843000010000000047330992708 + ``` + + You can also donate to [charity supporting Ukrainian army](https://savelife.in.ua/en/donate/). + + +Ask you politicians: +- stop Russia access to SWIFT +- impose no-fly zone over Ukraine + + ## Gopher: asynchronous implementation of go-like channels/selectors in scala ======= From b333be8945d3b4fef7499c7d0c25a122e427deed Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Sat, 16 Apr 2022 12:25:21 +0300 Subject: [PATCH 127/161] Update README.md Optimize Help Ukraine banner --- README.md | 31 +++++-------------------------- 1 file changed, 5 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index 014801f9..bb59d4e2 100644 --- a/README.md +++ b/README.md @@ -1,30 +1,9 @@ -# 🇺 🇦 UKRAINE NEEDS YOUR HELP NOW! +# 🇺 🇦 HELP UKRAINE - I'm the creator of this project and I'm Ukrainian. -**My country, Ukraine, [is being invaded by the Russian Federation, right now](https://www.bbc.com/news/world-europe-60504334)**. - -**Please, help to save my country!** -Ukrainian National Bank opened [an account to Raise Funds for Ukraine’s Armed Forces](https://bank.gov.ua/en/news/all/natsionalniy-bank-vidkriv-spetsrahunok-dlya-zboru-koshtiv-na-potrebi-armiyi): - - ``` - SWIFT Code NBU: NBUA UA UX - JP MORGAN CHASE BANK, New York - SWIFT Code: CHASUS33 - Account: 400807238 - 383 Madison Avenue, New York, NY 10179, USA - IBAN: UA843000010000000047330992708 - ``` - - You can also donate to [charity supporting Ukrainian army](https://savelife.in.ua/en/donate/). - - -Ask you politicians: -- stop Russia access to SWIFT -- impose no-fly zone over Ukraine - - - -## Gopher: asynchronous implementation of go-like channels/selectors in scala +I'm the creator of this project. +My country, Ukraine, [is being invaded by the Russian Federation, right now](https://war.ukraine.ua). If you want to help my country to fight, consider donating to [charity supporting Ukrainian army](https://www.comebackalive.in.ua/). More options is described on [support ukraine](https://supportukrainenow.org/) site. + +# Gopher: asynchronous implementation of go-like channels/selectors in scala ======= ### Dependences: From ae864747a1e43391e060a8025e742df3d204bbe0 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Mon, 6 Jun 2022 08:31:43 +0300 Subject: [PATCH 128/161] adopted to scala compiler 3.1.2 and dotty-cps-async-0.9.10-SNAPSHOT --- build.sbt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/build.sbt b/build.sbt index 9a8c5614..814a0979 100644 --- a/build.sbt +++ b/build.sbt @@ -1,5 +1,5 @@ //val dottyVersion = "3.0.0-RC2-bin-SNAPSHOT" -val dottyVersion = "3.1.1" +val dottyVersion = "3.1.2" //val dottyVersion = "3.1.3-RC1-bin-SNAPSHOT" //val dottyVersion = dottyLatestNightlyBuild.get @@ -11,7 +11,7 @@ val sharedSettings = Seq( scalaVersion := dottyVersion, name := "scala-gopher", resolvers += "Local Ivy Repository" at "file://"+Path.userHome.absolutePath+"/.ivy2/local", - libraryDependencies += "com.github.rssh" %%% "dotty-cps-async" % "0.9.8", + libraryDependencies += "com.github.rssh" %%% "dotty-cps-async" % "0.9.10-SNAPSHOT", libraryDependencies += "org.scalameta" %%% "munit" % "0.7.29" % Test, ) @@ -46,7 +46,7 @@ lazy val gopher = crossProject(JSPlatform, JVMPlatform) Compile / doc / scalacOptions := Seq("-groups", "-source-links:shared=github://rssh/scala-gopher/master#shared", "-source-links:jvm=github://rssh/scala-gopher/master#jvm"), - mimaPreviousArtifacts := Set( "com.github.rssh" %% "scala-gopher" % "3.0.1" ) + mimaPreviousArtifacts := Set( "com.github.rssh" %% "scala-gopher" % "3.0.2" ) ).jsSettings( libraryDependencies += ("org.scala-js" %%% "scalajs-java-logging" % "1.0.0").cross(CrossVersion.for3Use2_13), // TODO: switch to ModuleES ? From 1dced8ee614ae19999b31a898d4dd4d652144251 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Mon, 27 Jun 2022 22:52:38 +0300 Subject: [PATCH 129/161] better State representation in queens --- .../src/test/scala/gopher/monads/Queens.scala | 41 +++++++------------ 1 file changed, 14 insertions(+), 27 deletions(-) diff --git a/shared/src/test/scala/gopher/monads/Queens.scala b/shared/src/test/scala/gopher/monads/Queens.scala index 917b444d..7b48099d 100644 --- a/shared/src/test/scala/gopher/monads/Queens.scala +++ b/shared/src/test/scala/gopher/monads/Queens.scala @@ -16,29 +16,17 @@ class QueensSuite extends FunSuite { import scala.concurrent.ExecutionContext.Implicits.global given Gopher[Future] = SharedGopherAPI.apply[Future]() - case class State( - busyRows:Set[Int], - busyColumns:Set[Int], - busyLRDiagonals:Set[Int], - busyRLDiagonals:Set[Int], - queens: Vector[(Int,Int)] - ) { + type State = Vector[(Int,Int)] + + extension(queens:State) { - def isBusy(i:Int, j:Int): Boolean = - busyRows.contains(i) || - busyColumns.contains(j) || - busyLRDiagonals.contains(i-j) || - busyRLDiagonals.contains(i+j) + def isUnderAttack(i:Int, j:Int): Boolean = + queens.exists{ (qi,qj) => + qi == i || qj == j || i-j == qi-qj || i+j == qi+qj + } - def put(i:Int, j:Int): State = - copy( busyRows = busyRows + i, - busyColumns = busyColumns + j, - busyLRDiagonals = busyLRDiagonals + (i-j), - busyRLDiagonals = busyRLDiagonals + (i+j), - queens = queens :+ (i,j) - ) - + queens :+ (i,j) } @@ -47,9 +35,9 @@ class QueensSuite extends FunSuite { def putQueen(state:State): ReadChannel[Future,State] = val ch = makeChannel[State]() async[Future] { - val i = state.queens.length + val i = state.length if i < N then - for{ j <- 0 until N if !state.isBusy(i,j) } + for{ j <- 0 until N if !state.isUnderAttack(i,j) } ch.write(state.put(i,j)) ch.close() } @@ -57,22 +45,21 @@ class QueensSuite extends FunSuite { def solutions(state: State): ReadChannel[Future,State] = async[[X] =>> ReadChannel[Future,X]] { - if(state.queens.size < N) then + if(state.size < N) then val nextState = await(putQueen(state)) await(solutions(nextState)) else state } - val emptyState = State(Set.empty, Set.empty, Set.empty, Set.empty, Vector.empty) test("two first solution for 8 queens problem") { async[Future] { - val r = solutions(emptyState).take(2) + val r = solutions(Vector.empty).take(2) assert(!r.isEmpty) - println(r.map(_.queens)) + println(r) } } -} \ No newline at end of file +} From 1229bb51b0f1a26e9d27cd31c817fc0088fdfab0 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Mon, 27 Jun 2022 23:27:57 +0300 Subject: [PATCH 130/161] more condenced representation --- shared/src/test/scala/gopher/monads/Queens.scala | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/shared/src/test/scala/gopher/monads/Queens.scala b/shared/src/test/scala/gopher/monads/Queens.scala index 7b48099d..89edc8fc 100644 --- a/shared/src/test/scala/gopher/monads/Queens.scala +++ b/shared/src/test/scala/gopher/monads/Queens.scala @@ -16,17 +16,17 @@ class QueensSuite extends FunSuite { import scala.concurrent.ExecutionContext.Implicits.global given Gopher[Future] = SharedGopherAPI.apply[Future]() - type State = Vector[(Int,Int)] + type State = Vector[Int] extension(queens:State) { def isUnderAttack(i:Int, j:Int): Boolean = - queens.exists{ (qi,qj) => + queens.zipWithIndex.exists{ (qj,qi) => qi == i || qj == j || i-j == qi-qj || i+j == qi+qj } - - def put(i:Int, j:Int): State = - queens :+ (i,j) + + def asPairs:Vector[(Int,Int)] = + queens.zipWithIndex.map(_.swap) } @@ -38,14 +38,14 @@ class QueensSuite extends FunSuite { val i = state.length if i < N then for{ j <- 0 until N if !state.isUnderAttack(i,j) } - ch.write(state.put(i,j)) + ch.write(state appended j) ch.close() } ch def solutions(state: State): ReadChannel[Future,State] = async[[X] =>> ReadChannel[Future,X]] { - if(state.size < N) then + if(state.length < N) then val nextState = await(putQueen(state)) await(solutions(nextState)) else @@ -57,7 +57,7 @@ class QueensSuite extends FunSuite { async[Future] { val r = solutions(Vector.empty).take(2) assert(!r.isEmpty) - println(r) + println(r.map(_.asPairs)) } } From bf02dc8846acc783dbf9265e26fbaa63747ef1ea Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Tue, 12 Jul 2022 08:00:19 +0300 Subject: [PATCH 131/161] dotty-cps-async 0.9.10 --- build.sbt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/build.sbt b/build.sbt index 814a0979..e98e467d 100644 --- a/build.sbt +++ b/build.sbt @@ -3,15 +3,15 @@ val dottyVersion = "3.1.2" //val dottyVersion = "3.1.3-RC1-bin-SNAPSHOT" //val dottyVersion = dottyLatestNightlyBuild.get -ThisBuild/version := "3.0.2" +ThisBuild/version := "3.0.3" ThisBuild/versionScheme := Some("semver-spec") val sharedSettings = Seq( organization := "com.github.rssh", scalaVersion := dottyVersion, name := "scala-gopher", - resolvers += "Local Ivy Repository" at "file://"+Path.userHome.absolutePath+"/.ivy2/local", - libraryDependencies += "com.github.rssh" %%% "dotty-cps-async" % "0.9.10-SNAPSHOT", + //resolvers += "Local Ivy Repository" at "file://"+Path.userHome.absolutePath+"/.ivy2/local", + libraryDependencies += "com.github.rssh" %%% "dotty-cps-async" % "0.9.10", libraryDependencies += "org.scalameta" %%% "munit" % "0.7.29" % Test, ) From 3a5f845589f1a21b3233f8feee1cc8f707115805 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Tue, 12 Jul 2022 08:00:48 +0300 Subject: [PATCH 132/161] scala-3.1.3 --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index e98e467d..e77eb805 100644 --- a/build.sbt +++ b/build.sbt @@ -1,5 +1,5 @@ //val dottyVersion = "3.0.0-RC2-bin-SNAPSHOT" -val dottyVersion = "3.1.2" +val dottyVersion = "3.1.3" //val dottyVersion = "3.1.3-RC1-bin-SNAPSHOT" //val dottyVersion = dottyLatestNightlyBuild.get From ef461c78093c5b4407a334bc259a94e63d90372f Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Tue, 12 Jul 2022 08:03:50 +0300 Subject: [PATCH 133/161] scalajs-0.10.1 --- project/plugins.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/plugins.sbt b/project/plugins.sbt index 0b41d0fd..77363e3d 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -2,5 +2,5 @@ addSbtPlugin("com.jsuereth" % "sbt-pgp" % "2.0.2") addSbtPlugin("com.typesafe.sbt" % "sbt-site" % "1.4.1") addSbtPlugin("com.typesafe.sbt" % "sbt-ghpages" % "0.6.3") addSbtPlugin("org.portable-scala" % "sbt-scalajs-crossproject" % "1.0.0") -addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.8.0") +addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.10.1") addSbtPlugin("com.typesafe" % "sbt-mima-plugin" % "1.0.1") From ae8bcf200ebf31c06514954a34995c9081ec6709 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Sat, 17 Sep 2022 18:01:18 +0300 Subject: [PATCH 134/161] adopted to dotty-cps-asymnc 0.9.11 --- build.sbt | 6 +++--- project/build.properties | 2 +- project/plugins.sbt | 2 +- shared/src/main/scala/gopher/SelectMacro.scala | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/build.sbt b/build.sbt index e77eb805..ec600462 100644 --- a/build.sbt +++ b/build.sbt @@ -1,9 +1,9 @@ //val dottyVersion = "3.0.0-RC2-bin-SNAPSHOT" -val dottyVersion = "3.1.3" +val dottyVersion = "3.2.0" //val dottyVersion = "3.1.3-RC1-bin-SNAPSHOT" //val dottyVersion = dottyLatestNightlyBuild.get -ThisBuild/version := "3.0.3" +ThisBuild/version := "3.0.4" ThisBuild/versionScheme := Some("semver-spec") val sharedSettings = Seq( @@ -11,7 +11,7 @@ val sharedSettings = Seq( scalaVersion := dottyVersion, name := "scala-gopher", //resolvers += "Local Ivy Repository" at "file://"+Path.userHome.absolutePath+"/.ivy2/local", - libraryDependencies += "com.github.rssh" %%% "dotty-cps-async" % "0.9.10", + libraryDependencies += "com.github.rssh" %%% "dotty-cps-async" % "0.9.11-SNAPSHOT", libraryDependencies += "org.scalameta" %%% "munit" % "0.7.29" % Test, ) diff --git a/project/build.properties b/project/build.properties index c8fcab54..22af2628 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=1.6.2 +sbt.version=1.7.1 diff --git a/project/plugins.sbt b/project/plugins.sbt index 77363e3d..3fe53801 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -2,5 +2,5 @@ addSbtPlugin("com.jsuereth" % "sbt-pgp" % "2.0.2") addSbtPlugin("com.typesafe.sbt" % "sbt-site" % "1.4.1") addSbtPlugin("com.typesafe.sbt" % "sbt-ghpages" % "0.6.3") addSbtPlugin("org.portable-scala" % "sbt-scalajs-crossproject" % "1.0.0") -addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.10.1") +addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.11.0") addSbtPlugin("com.typesafe" % "sbt-mima-plugin" % "1.0.1") diff --git a/shared/src/main/scala/gopher/SelectMacro.scala b/shared/src/main/scala/gopher/SelectMacro.scala index 23639188..6d53d3ad 100644 --- a/shared/src/main/scala/gopher/SelectMacro.scala +++ b/shared/src/main/scala/gopher/SelectMacro.scala @@ -13,7 +13,7 @@ import scala.util.control.NonFatal object SelectMacro: - import cps.macros.forest.TransformUtil + import cps.macros.common.TransformUtil sealed trait SelectGroupExpr[F[_],S, R]: def toExprOf[X <: SelectListeners[F,S, R]]: Expr[X] From 9fde6c061093db594215094ae732c78d3aa56a23 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Sat, 17 Sep 2022 22:08:56 +0300 Subject: [PATCH 135/161] 3.0.4 with dotty-cps-async 0.9.11 --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index ec600462..2a81f49e 100644 --- a/build.sbt +++ b/build.sbt @@ -11,7 +11,7 @@ val sharedSettings = Seq( scalaVersion := dottyVersion, name := "scala-gopher", //resolvers += "Local Ivy Repository" at "file://"+Path.userHome.absolutePath+"/.ivy2/local", - libraryDependencies += "com.github.rssh" %%% "dotty-cps-async" % "0.9.11-SNAPSHOT", + libraryDependencies += "com.github.rssh" %%% "dotty-cps-async" % "0.9.11", libraryDependencies += "org.scalameta" %%% "munit" % "0.7.29" % Test, ) From 173dd0c89c3d99f7edc2437dd4da93245c8b3961 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Thu, 8 Dec 2022 10:12:34 +0200 Subject: [PATCH 136/161] scaal-js 1.12.0 --- project/plugins.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/plugins.sbt b/project/plugins.sbt index 3fe53801..9b560f4d 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -2,5 +2,5 @@ addSbtPlugin("com.jsuereth" % "sbt-pgp" % "2.0.2") addSbtPlugin("com.typesafe.sbt" % "sbt-site" % "1.4.1") addSbtPlugin("com.typesafe.sbt" % "sbt-ghpages" % "0.6.3") addSbtPlugin("org.portable-scala" % "sbt-scalajs-crossproject" % "1.0.0") -addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.11.0") +addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.12.0") addSbtPlugin("com.typesafe" % "sbt-mima-plugin" % "1.0.1") From 4367720f4877db2f535ba0466f4e139629f3c939 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Thu, 8 Dec 2022 10:13:38 +0200 Subject: [PATCH 137/161] sbt 1.8.0 --- project/build.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/build.properties b/project/build.properties index 22af2628..8b9a0b0a 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=1.7.1 +sbt.version=1.8.0 From f80cba2ca2d39b1b492e34f823da5b44b949736d Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Thu, 8 Dec 2022 10:13:48 +0200 Subject: [PATCH 138/161] dotty-cps-async 0.9.12 --- build.sbt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.sbt b/build.sbt index 2a81f49e..379b1d20 100644 --- a/build.sbt +++ b/build.sbt @@ -1,5 +1,5 @@ //val dottyVersion = "3.0.0-RC2-bin-SNAPSHOT" -val dottyVersion = "3.2.0" +val dottyVersion = "3.2.1" //val dottyVersion = "3.1.3-RC1-bin-SNAPSHOT" //val dottyVersion = dottyLatestNightlyBuild.get @@ -11,7 +11,7 @@ val sharedSettings = Seq( scalaVersion := dottyVersion, name := "scala-gopher", //resolvers += "Local Ivy Repository" at "file://"+Path.userHome.absolutePath+"/.ivy2/local", - libraryDependencies += "com.github.rssh" %%% "dotty-cps-async" % "0.9.11", + libraryDependencies += "com.github.rssh" %%% "dotty-cps-async" % "0.9.12", libraryDependencies += "org.scalameta" %%% "munit" % "0.7.29" % Test, ) From 317bc0620c54192a06e36da339a6b2e8fd998b8f Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Thu, 8 Dec 2022 10:14:32 +0200 Subject: [PATCH 139/161] 3.0.5 release --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index 379b1d20..d47b57c5 100644 --- a/build.sbt +++ b/build.sbt @@ -3,7 +3,7 @@ val dottyVersion = "3.2.1" //val dottyVersion = "3.1.3-RC1-bin-SNAPSHOT" //val dottyVersion = dottyLatestNightlyBuild.get -ThisBuild/version := "3.0.4" +ThisBuild/version := "3.0.5" ThisBuild/versionScheme := Some("semver-spec") val sharedSettings = Seq( From 0bda21dd7374c4b760f818dce63e570c79b02507 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Fri, 23 Dec 2022 21:39:14 +0200 Subject: [PATCH 140/161] make snapshot dependency --- build.sbt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/build.sbt b/build.sbt index d47b57c5..ff94fdd4 100644 --- a/build.sbt +++ b/build.sbt @@ -3,7 +3,7 @@ val dottyVersion = "3.2.1" //val dottyVersion = "3.1.3-RC1-bin-SNAPSHOT" //val dottyVersion = dottyLatestNightlyBuild.get -ThisBuild/version := "3.0.5" +ThisBuild/version := "3.0.6-SNAPSHOT" ThisBuild/versionScheme := Some("semver-spec") val sharedSettings = Seq( @@ -11,7 +11,7 @@ val sharedSettings = Seq( scalaVersion := dottyVersion, name := "scala-gopher", //resolvers += "Local Ivy Repository" at "file://"+Path.userHome.absolutePath+"/.ivy2/local", - libraryDependencies += "com.github.rssh" %%% "dotty-cps-async" % "0.9.12", + libraryDependencies += "com.github.rssh" %%% "dotty-cps-async" % "0.9.14-SNAPSHOT", libraryDependencies += "org.scalameta" %%% "munit" % "0.7.29" % Test, ) @@ -34,7 +34,7 @@ lazy val gopher = crossProject(JSPlatform, JVMPlatform) .disablePlugins(SitePlugin) .disablePlugins(SitePreviewPlugin) .jvmSettings( - scalacOptions ++= Seq( "-unchecked", "-Ycheck:macros", "-uniqid", "-Xprint:types", "-explain" ), + scalacOptions ++= Seq( "-unchecked", "-Xcheck-macros", "-Ycheck:macro", "-uniqid", "-Xprint:types", "-explain" ), fork := true, /* javaOptions ++= Seq( @@ -46,7 +46,7 @@ lazy val gopher = crossProject(JSPlatform, JVMPlatform) Compile / doc / scalacOptions := Seq("-groups", "-source-links:shared=github://rssh/scala-gopher/master#shared", "-source-links:jvm=github://rssh/scala-gopher/master#jvm"), - mimaPreviousArtifacts := Set( "com.github.rssh" %% "scala-gopher" % "3.0.2" ) + mimaPreviousArtifacts := Set( "com.github.rssh" %% "scala-gopher" % "3.0.5" ) ).jsSettings( libraryDependencies += ("org.scala-js" %%% "scalajs-java-logging" % "1.0.0").cross(CrossVersion.for3Use2_13), // TODO: switch to ModuleES ? From c89863cdc2dfad53388bb8f1caaa5904d644fa1b Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Mon, 23 Jan 2023 14:47:34 +0200 Subject: [PATCH 141/161] dependency updates --- build.sbt | 6 +++--- project/build.properties | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/build.sbt b/build.sbt index ff94fdd4..7315b71f 100644 --- a/build.sbt +++ b/build.sbt @@ -1,9 +1,9 @@ //val dottyVersion = "3.0.0-RC2-bin-SNAPSHOT" -val dottyVersion = "3.2.1" +val dottyVersion = "3.2.2" //val dottyVersion = "3.1.3-RC1-bin-SNAPSHOT" //val dottyVersion = dottyLatestNightlyBuild.get -ThisBuild/version := "3.0.6-SNAPSHOT" +ThisBuild/version := "3.0.6" ThisBuild/versionScheme := Some("semver-spec") val sharedSettings = Seq( @@ -11,7 +11,7 @@ val sharedSettings = Seq( scalaVersion := dottyVersion, name := "scala-gopher", //resolvers += "Local Ivy Repository" at "file://"+Path.userHome.absolutePath+"/.ivy2/local", - libraryDependencies += "com.github.rssh" %%% "dotty-cps-async" % "0.9.14-SNAPSHOT", + libraryDependencies += "com.github.rssh" %%% "dotty-cps-async" % "0.9.15", libraryDependencies += "org.scalameta" %%% "munit" % "0.7.29" % Test, ) diff --git a/project/build.properties b/project/build.properties index 8b9a0b0a..46e43a97 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=1.8.0 +sbt.version=1.8.2 From 401a5b9fe98049c52e58b860104bfc6e970ba2ab Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Mon, 23 Jan 2023 14:52:48 +0200 Subject: [PATCH 142/161] 3.0.7-SNAPSHOT --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index 7315b71f..ad71ffd8 100644 --- a/build.sbt +++ b/build.sbt @@ -3,7 +3,7 @@ val dottyVersion = "3.2.2" //val dottyVersion = "3.1.3-RC1-bin-SNAPSHOT" //val dottyVersion = dottyLatestNightlyBuild.get -ThisBuild/version := "3.0.6" +ThisBuild/version := "3.0.7-SNAPSHOT" ThisBuild/versionScheme := Some("semver-spec") val sharedSettings = Seq( From 987728ef44148b8cf4080071d775643cb98ac16e Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Tue, 14 Feb 2023 22:08:45 +0200 Subject: [PATCH 143/161] scala-js 1.13.0 --- project/plugins.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/plugins.sbt b/project/plugins.sbt index 9b560f4d..bdfe9a05 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -2,5 +2,5 @@ addSbtPlugin("com.jsuereth" % "sbt-pgp" % "2.0.2") addSbtPlugin("com.typesafe.sbt" % "sbt-site" % "1.4.1") addSbtPlugin("com.typesafe.sbt" % "sbt-ghpages" % "0.6.3") addSbtPlugin("org.portable-scala" % "sbt-scalajs-crossproject" % "1.0.0") -addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.12.0") +addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.13.0") addSbtPlugin("com.typesafe" % "sbt-mima-plugin" % "1.0.1") From e99d8bc0b10208286dbbae20a084d2dad8d20efa Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Tue, 14 Feb 2023 22:09:18 +0200 Subject: [PATCH 144/161] dotty-cps-async snapshot --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index ad71ffd8..1f07a425 100644 --- a/build.sbt +++ b/build.sbt @@ -11,7 +11,7 @@ val sharedSettings = Seq( scalaVersion := dottyVersion, name := "scala-gopher", //resolvers += "Local Ivy Repository" at "file://"+Path.userHome.absolutePath+"/.ivy2/local", - libraryDependencies += "com.github.rssh" %%% "dotty-cps-async" % "0.9.15", + libraryDependencies += "com.github.rssh" %%% "dotty-cps-async" % "0.9.16-SNAPSHOT", libraryDependencies += "org.scalameta" %%% "munit" % "0.7.29" % Test, ) From dcb32420271bfccacab85c33861f1a8536d8c73f Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Mon, 20 Feb 2023 08:01:24 +0200 Subject: [PATCH 145/161] updates dotty-cps-async to 0.9.16 --- README.md | 2 +- build.sbt | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index bb59d4e2..807c3486 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ My country, Ukraine, [is being invaded by the Russian Federation, right now](htt For scala 3.1.1+: - libraryDependencies += "com.github.rssh" %% "scala-gopher" % "3.0.2" + libraryDependencies += "com.github.rssh" %% "scala-gopher" % "3.0.7" For scala 3 and 3.1.0: diff --git a/build.sbt b/build.sbt index 1f07a425..9c3b45c6 100644 --- a/build.sbt +++ b/build.sbt @@ -3,7 +3,7 @@ val dottyVersion = "3.2.2" //val dottyVersion = "3.1.3-RC1-bin-SNAPSHOT" //val dottyVersion = dottyLatestNightlyBuild.get -ThisBuild/version := "3.0.7-SNAPSHOT" +ThisBuild/version := "3.0.7" ThisBuild/versionScheme := Some("semver-spec") val sharedSettings = Seq( @@ -11,7 +11,7 @@ val sharedSettings = Seq( scalaVersion := dottyVersion, name := "scala-gopher", //resolvers += "Local Ivy Repository" at "file://"+Path.userHome.absolutePath+"/.ivy2/local", - libraryDependencies += "com.github.rssh" %%% "dotty-cps-async" % "0.9.16-SNAPSHOT", + libraryDependencies += "com.github.rssh" %%% "dotty-cps-async" % "0.9.16", libraryDependencies += "org.scalameta" %%% "munit" % "0.7.29" % Test, ) From bd6e00c78d68d8ae3fc1a3fa7bfb94352ace9bfc Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Sat, 10 Jun 2023 12:23:21 +0300 Subject: [PATCH 146/161] adopted to upcoming dotty-cps-async release --- build.sbt | 10 ++++++---- shared/src/main/scala/gopher/ReadChannel.scala | 13 +++++++------ shared/src/main/scala/gopher/SelectListeners.scala | 2 +- shared/src/main/scala/gopher/SelectMacro.scala | 2 +- shared/src/main/scala/gopher/WriteChannel.scala | 8 ++++---- .../scala/gopher/monads/ReadChannelCpsMonad.scala | 2 +- 6 files changed, 20 insertions(+), 17 deletions(-) diff --git a/build.sbt b/build.sbt index 9c3b45c6..3f707e1d 100644 --- a/build.sbt +++ b/build.sbt @@ -1,9 +1,9 @@ //val dottyVersion = "3.0.0-RC2-bin-SNAPSHOT" -val dottyVersion = "3.2.2" +val dottyVersion = "3.3.0" //val dottyVersion = "3.1.3-RC1-bin-SNAPSHOT" //val dottyVersion = dottyLatestNightlyBuild.get -ThisBuild/version := "3.0.7" +ThisBuild/version := "3.0.8-SNAPSHOT" ThisBuild/versionScheme := Some("semver-spec") val sharedSettings = Seq( @@ -11,7 +11,7 @@ val sharedSettings = Seq( scalaVersion := dottyVersion, name := "scala-gopher", //resolvers += "Local Ivy Repository" at "file://"+Path.userHome.absolutePath+"/.ivy2/local", - libraryDependencies += "com.github.rssh" %%% "dotty-cps-async" % "0.9.16", + libraryDependencies += "com.github.rssh" %%% "dotty-cps-async" % "0.9.17-RC1", libraryDependencies += "org.scalameta" %%% "munit" % "0.7.29" % Test, ) @@ -34,7 +34,9 @@ lazy val gopher = crossProject(JSPlatform, JVMPlatform) .disablePlugins(SitePlugin) .disablePlugins(SitePreviewPlugin) .jvmSettings( - scalacOptions ++= Seq( "-unchecked", "-Xcheck-macros", "-Ycheck:macro", "-uniqid", "-Xprint:types", "-explain" ), + //scalacOptions ++= Seq( "-unchecked", "-Xcheck-macros", "-Ycheck:macro", "-uniqid", "-Xprint:types", "-explain" ), + // Error in dotty + scalacOptions ++= Seq( "-unchecked", "-Xprint:types" ), fork := true, /* javaOptions ++= Seq( diff --git a/shared/src/main/scala/gopher/ReadChannel.scala b/shared/src/main/scala/gopher/ReadChannel.scala index 5b124b7a..8daf3eb7 100644 --- a/shared/src/main/scala/gopher/ReadChannel.scala +++ b/shared/src/main/scala/gopher/ReadChannel.scala @@ -52,12 +52,13 @@ trait ReadChannel[F[_], A]: * Can be used only inside async block. * If stream is closed and no values to read left in the stream - throws StreamClosedException **/ - transparent inline def read[G[_]]()(using mc:CpsMonadContext[G]): A = await(aread())(using rAsyncMonad, mc) + transparent inline def read[G[_]]()(using mc:CpsMonadContext[G], fg:CpsMonadConversion[F,G]): A = + await(aread()) /** * Synonim for read. */ - transparent inline def ?(using mc:CpsMonadContext[F]) : A = await(aread())(using rAsyncMonad, mc) + transparent inline def ?(using mc:CpsMonadContext[F]) : A = await(aread()) /** * return F which contains sequence from first `n` elements. @@ -84,7 +85,7 @@ trait ReadChannel[F[_], A]: * should be called inside async block. **/ transparent inline def take(n: Int)(using CpsMonadContext[F]): IndexedSeq[A] = - await(atake(n))(using rAsyncMonad) + await(atake(n)) /** * read value and return future with @@ -107,7 +108,7 @@ trait ReadChannel[F[_], A]: * * should be called inside async block. **/ - transparent inline def optRead()(using CpsMonadContext[F]): Option[A] = await(aOptRead())(using rAsyncMonad) + transparent inline def optRead()(using CpsMonadContext[F]): Option[A] = await(aOptRead()) def foreach_async(f: A=>F[Unit]): F[Unit] = given CpsAsyncMonad[F] = asyncMonad @@ -131,7 +132,7 @@ trait ReadChannel[F[_], A]: * until end of stream is not reached **/ transparent inline def foreach(inline f: A=>Unit)(using CpsMonadContext[F]): Unit = - await(aforeach(f))(using rAsyncMonad) + await(aforeach(f)) def map[B](f: A=>B): ReadChannel[F,B] = @@ -171,7 +172,7 @@ trait ReadChannel[F[_], A]: } transparent inline def fold[S](inline s0:S)(inline f: (S,A) => S )(using mc:CpsMonadContext[F]): S = - await[F,S,F](afold(s0)(f))(using rAsyncMonad, mc) + await[F,S,F](afold(s0)(f)) def zip[B](x: ReadChannel[F,B]): ReadChannel[F,(A,B)] = given CpsSchedulingMonad[F] = asyncMonad diff --git a/shared/src/main/scala/gopher/SelectListeners.scala b/shared/src/main/scala/gopher/SelectListeners.scala index 14440418..c3de322b 100644 --- a/shared/src/main/scala/gopher/SelectListeners.scala +++ b/shared/src/main/scala/gopher/SelectListeners.scala @@ -16,7 +16,7 @@ trait SelectListeners[F[_],S, R]: def runAsync():F[R] - transparent inline def run()(using CpsMonadContext[F]): R = await(runAsync())(using asyncMonad) + transparent inline def run()(using CpsMonadContext[F]): R = await(runAsync()) diff --git a/shared/src/main/scala/gopher/SelectMacro.scala b/shared/src/main/scala/gopher/SelectMacro.scala index 6d53d3ad..fd93363c 100644 --- a/shared/src/main/scala/gopher/SelectMacro.scala +++ b/shared/src/main/scala/gopher/SelectMacro.scala @@ -56,7 +56,7 @@ object SelectMacro: )(using Quotes): Expr[R] = val g = selectListenerBuilder(constructor, caseDefs) // dotty bug if g.run - val r = '{ await($g.runAsync())(using ${api}.asyncMonad, $monadContext) } + val r = '{ await($g.runAsync())(using $monadContext, CpsMonadConversion.identityConversion[F]) } r.asExprOf[R] def buildSelectListenerRunAsync[F[_]:Type, S:Type, R:Type, L <: SelectListeners[F,S,R]:Type]( diff --git a/shared/src/main/scala/gopher/WriteChannel.scala b/shared/src/main/scala/gopher/WriteChannel.scala index 758455f8..a98c0960 100644 --- a/shared/src/main/scala/gopher/WriteChannel.scala +++ b/shared/src/main/scala/gopher/WriteChannel.scala @@ -23,13 +23,13 @@ trait WriteChannel[F[_], A]: // inline def apply(a:A): Unit = await(awrite(a))(using asyncMonad) // inline def unapply(a:A): Some[A] = ??? - transparent inline def write(inline a:A)(using CpsMonadContext[F]): Unit = await(awrite(a))(using asyncMonad) + transparent inline def write(inline a:A)(using CpsMonadContext[F]): Unit = await(awrite(a)) @targetName("write1") - transparent inline def <~ (inline a:A)(using CpsMonadContext[F]): Unit = await(awrite(a))(using asyncMonad) + transparent inline def <~ (inline a:A)(using CpsMonadContext[F]): Unit = await(awrite(a)) @targetName("write2") - transparent inline def ! (inline a:A)(using CpsMonadContext[F]): Unit = await(awrite(a))(using asyncMonad) + transparent inline def ! (inline a:A)(using CpsMonadContext[F]): Unit = await(awrite(a)) //def Write(x:A):WritePattern = new WritePattern(x) @@ -52,7 +52,7 @@ trait WriteChannel[F[_], A]: } transparent inline def writeAll(inline collection: IterableOnce[A])(using mc: CpsMonadContext[F]): Unit = - await(awriteAll(collection))(using asyncMonad, mc) + await(awriteAll(collection)) def withWriteExpiration(ttl: FiniteDuration, throwTimeouts: Boolean)(using gopherApi: Gopher[F]): WriteChannelWithExpiration[F,A] = diff --git a/shared/src/main/scala/gopher/monads/ReadChannelCpsMonad.scala b/shared/src/main/scala/gopher/monads/ReadChannelCpsMonad.scala index b936ae4f..24f17f01 100644 --- a/shared/src/main/scala/gopher/monads/ReadChannelCpsMonad.scala +++ b/shared/src/main/scala/gopher/monads/ReadChannelCpsMonad.scala @@ -7,7 +7,7 @@ import gopher.impl._ -given ReadChannelCpsMonad[F[_]](using Gopher[F]): CpsMonadInstanceContext[[A] =>> ReadChannel[F,A]] with +given ReadChannelCpsMonad[F[_]](using Gopher[F]): CpsPureMonadInstanceContext[[A] =>> ReadChannel[F,A]] with def pure[T](t:T): ReadChannel[F,T] = From 1a7514be38a701fbd29bdf73efa55fcb5aabe9ca Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Sun, 11 Jun 2023 10:11:21 +0300 Subject: [PATCH 147/161] sbt-1.9.0 --- project/build.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/build.properties b/project/build.properties index 46e43a97..40b3b8e7 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=1.8.2 +sbt.version=1.9.0 From 7ce8721cdf1d5247b4d0cb0e0d7bbb4611f9e961 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Sun, 11 Jun 2023 10:27:35 +0300 Subject: [PATCH 148/161] 4.0.0 snapshot due to binary incompability caused by dotty-cps-async versions --- build.sbt | 8 ++++---- project/plugins.sbt | 6 ++++-- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/build.sbt b/build.sbt index 3f707e1d..9bcf0428 100644 --- a/build.sbt +++ b/build.sbt @@ -3,7 +3,7 @@ val dottyVersion = "3.3.0" //val dottyVersion = "3.1.3-RC1-bin-SNAPSHOT" //val dottyVersion = dottyLatestNightlyBuild.get -ThisBuild/version := "3.0.8-SNAPSHOT" +ThisBuild/version := "4.0.0-SNAPSHOT" ThisBuild/versionScheme := Some("semver-spec") val sharedSettings = Seq( @@ -11,8 +11,8 @@ val sharedSettings = Seq( scalaVersion := dottyVersion, name := "scala-gopher", //resolvers += "Local Ivy Repository" at "file://"+Path.userHome.absolutePath+"/.ivy2/local", - libraryDependencies += "com.github.rssh" %%% "dotty-cps-async" % "0.9.17-RC1", - libraryDependencies += "org.scalameta" %%% "munit" % "0.7.29" % Test, + libraryDependencies += "com.github.rssh" %%% "dotty-cps-async" % "0.9.17", + libraryDependencies += "org.scalameta" %%% "munit" % "1.0.0-M8" % Test, ) lazy val root = project @@ -48,7 +48,7 @@ lazy val gopher = crossProject(JSPlatform, JVMPlatform) Compile / doc / scalacOptions := Seq("-groups", "-source-links:shared=github://rssh/scala-gopher/master#shared", "-source-links:jvm=github://rssh/scala-gopher/master#jvm"), - mimaPreviousArtifacts := Set( "com.github.rssh" %% "scala-gopher" % "3.0.5" ) + mimaPreviousArtifacts := Set( "com.github.rssh" %% "scala-gopher" % "3.0.7" ) ).jsSettings( libraryDependencies += ("org.scala-js" %%% "scalajs-java-logging" % "1.0.0").cross(CrossVersion.for3Use2_13), // TODO: switch to ModuleES ? diff --git a/project/plugins.sbt b/project/plugins.sbt index bdfe9a05..9e8d881c 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,6 +1,8 @@ addSbtPlugin("com.jsuereth" % "sbt-pgp" % "2.0.2") addSbtPlugin("com.typesafe.sbt" % "sbt-site" % "1.4.1") addSbtPlugin("com.typesafe.sbt" % "sbt-ghpages" % "0.6.3") -addSbtPlugin("org.portable-scala" % "sbt-scalajs-crossproject" % "1.0.0") -addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.13.0") +addSbtPlugin("org.portable-scala" % "sbt-scala-native-crossproject" % "1.1.0") +addSbtPlugin("org.portable-scala" % "sbt-scalajs-crossproject" % "1.1.0") +addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.13.1") +addSbtPlugin("org.scala-native" % "sbt-scala-native" % "0.4.14") addSbtPlugin("com.typesafe" % "sbt-mima-plugin" % "1.0.1") From 810fcc89aa2637acff429c89d085713dcfed0ad3 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Sun, 11 Jun 2023 10:33:09 +0300 Subject: [PATCH 149/161] 4.0.1-SNAPSHOT --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index 9bcf0428..f3911ef2 100644 --- a/build.sbt +++ b/build.sbt @@ -3,7 +3,7 @@ val dottyVersion = "3.3.0" //val dottyVersion = "3.1.3-RC1-bin-SNAPSHOT" //val dottyVersion = dottyLatestNightlyBuild.get -ThisBuild/version := "4.0.0-SNAPSHOT" +ThisBuild/version := "4.0.1-SHAPSHOT" ThisBuild/versionScheme := Some("semver-spec") val sharedSettings = Seq( From 1315eb6642cfd3d6a672e4745c5a49961014fdb6 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Mon, 12 Jun 2023 23:57:27 +0300 Subject: [PATCH 150/161] rev.to 4.0.0 for tag --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index f3911ef2..475bbaad 100644 --- a/build.sbt +++ b/build.sbt @@ -3,7 +3,7 @@ val dottyVersion = "3.3.0" //val dottyVersion = "3.1.3-RC1-bin-SNAPSHOT" //val dottyVersion = dottyLatestNightlyBuild.get -ThisBuild/version := "4.0.1-SHAPSHOT" +ThisBuild/version := "4.0.0" ThisBuild/versionScheme := Some("semver-spec") val sharedSettings = Seq( From ca68d51ef7c0f3dd3366a24a28f1ac2d9ab0bb28 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Mon, 21 Aug 2023 15:15:41 +0300 Subject: [PATCH 151/161] 4.0.1 release for use with scala-gopher 0.4.18 --- build.sbt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/build.sbt b/build.sbt index 475bbaad..19b427e8 100644 --- a/build.sbt +++ b/build.sbt @@ -3,7 +3,7 @@ val dottyVersion = "3.3.0" //val dottyVersion = "3.1.3-RC1-bin-SNAPSHOT" //val dottyVersion = dottyLatestNightlyBuild.get -ThisBuild/version := "4.0.0" +ThisBuild/version := "4.0.1" ThisBuild/versionScheme := Some("semver-spec") val sharedSettings = Seq( @@ -11,7 +11,7 @@ val sharedSettings = Seq( scalaVersion := dottyVersion, name := "scala-gopher", //resolvers += "Local Ivy Repository" at "file://"+Path.userHome.absolutePath+"/.ivy2/local", - libraryDependencies += "com.github.rssh" %%% "dotty-cps-async" % "0.9.17", + libraryDependencies += "com.github.rssh" %%% "dotty-cps-async" % "0.9.18", libraryDependencies += "org.scalameta" %%% "munit" % "1.0.0-M8" % Test, ) @@ -48,7 +48,7 @@ lazy val gopher = crossProject(JSPlatform, JVMPlatform) Compile / doc / scalacOptions := Seq("-groups", "-source-links:shared=github://rssh/scala-gopher/master#shared", "-source-links:jvm=github://rssh/scala-gopher/master#jvm"), - mimaPreviousArtifacts := Set( "com.github.rssh" %% "scala-gopher" % "3.0.7" ) + mimaPreviousArtifacts := Set( "com.github.rssh" %% "scala-gopher" % "4.0.0" ) ).jsSettings( libraryDependencies += ("org.scala-js" %%% "scalajs-java-logging" % "1.0.0").cross(CrossVersion.for3Use2_13), // TODO: switch to ModuleES ? From c27f1dc77643f4cc7604dd3ef84c235e938d5e56 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Mon, 2 Oct 2023 09:59:49 +0300 Subject: [PATCH 152/161] adopted to scala-3.3.1, dotty-cps-async 0.9.19 --- build.sbt | 8 ++++---- project/build.properties | 2 +- project/plugins.sbt | 2 +- shared/src/main/scala/gopher/ReadChannel.scala | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/build.sbt b/build.sbt index 19b427e8..3c1f1859 100644 --- a/build.sbt +++ b/build.sbt @@ -1,9 +1,9 @@ //val dottyVersion = "3.0.0-RC2-bin-SNAPSHOT" -val dottyVersion = "3.3.0" +val dottyVersion = "3.3.1" //val dottyVersion = "3.1.3-RC1-bin-SNAPSHOT" //val dottyVersion = dottyLatestNightlyBuild.get -ThisBuild/version := "4.0.1" +ThisBuild/version := "4.0.2" ThisBuild/versionScheme := Some("semver-spec") val sharedSettings = Seq( @@ -11,8 +11,8 @@ val sharedSettings = Seq( scalaVersion := dottyVersion, name := "scala-gopher", //resolvers += "Local Ivy Repository" at "file://"+Path.userHome.absolutePath+"/.ivy2/local", - libraryDependencies += "com.github.rssh" %%% "dotty-cps-async" % "0.9.18", - libraryDependencies += "org.scalameta" %%% "munit" % "1.0.0-M8" % Test, + libraryDependencies += "com.github.rssh" %%% "dotty-cps-async" % "0.9.19", + libraryDependencies += "org.scalameta" %%% "munit" % "1.0.0-M10" % Test, ) lazy val root = project diff --git a/project/build.properties b/project/build.properties index 40b3b8e7..27430827 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=1.9.0 +sbt.version=1.9.6 diff --git a/project/plugins.sbt b/project/plugins.sbt index 9e8d881c..e8e217d2 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -3,6 +3,6 @@ addSbtPlugin("com.typesafe.sbt" % "sbt-site" % "1.4.1") addSbtPlugin("com.typesafe.sbt" % "sbt-ghpages" % "0.6.3") addSbtPlugin("org.portable-scala" % "sbt-scala-native-crossproject" % "1.1.0") addSbtPlugin("org.portable-scala" % "sbt-scalajs-crossproject" % "1.1.0") -addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.13.1") +addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.14.0") addSbtPlugin("org.scala-native" % "sbt-scala-native" % "0.4.14") addSbtPlugin("com.typesafe" % "sbt-mima-plugin" % "1.0.1") diff --git a/shared/src/main/scala/gopher/ReadChannel.scala b/shared/src/main/scala/gopher/ReadChannel.scala index 8daf3eb7..e02ae609 100644 --- a/shared/src/main/scala/gopher/ReadChannel.scala +++ b/shared/src/main/scala/gopher/ReadChannel.scala @@ -36,7 +36,7 @@ trait ReadChannel[F[_], A]: def addDoneReader(reader: Reader[Unit]): Unit - lazy val done: ReadChannel[F,Unit] = DoneReadChannel() + final lazy val done: ReadChannel[F,Unit] = DoneReadChannel() type done = Unit From 3c6ffef3fea62433dcb11c57182d937cae8f4513 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Mon, 2 Oct 2023 10:09:40 +0300 Subject: [PATCH 153/161] updated versions --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 807c3486..4c9fbc33 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ My country, Ukraine, [is being invaded by the Russian Federation, right now](htt For scala 3.1.1+: - libraryDependencies += "com.github.rssh" %% "scala-gopher" % "3.0.7" + libraryDependencies += "com.github.rssh" %% "scala-gopher" % "4.0.2" For scala 3 and 3.1.0: From aa716e3a8985cfd7056854d8e27d6e47f402485b Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Wed, 31 Jan 2024 12:40:39 +0200 Subject: [PATCH 154/161] scalajs 0.15.0 --- project/plugins.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/plugins.sbt b/project/plugins.sbt index e8e217d2..d3ea172d 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -3,6 +3,6 @@ addSbtPlugin("com.typesafe.sbt" % "sbt-site" % "1.4.1") addSbtPlugin("com.typesafe.sbt" % "sbt-ghpages" % "0.6.3") addSbtPlugin("org.portable-scala" % "sbt-scala-native-crossproject" % "1.1.0") addSbtPlugin("org.portable-scala" % "sbt-scalajs-crossproject" % "1.1.0") -addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.14.0") +addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.15.0") addSbtPlugin("org.scala-native" % "sbt-scala-native" % "0.4.14") addSbtPlugin("com.typesafe" % "sbt-mima-plugin" % "1.0.1") From 7ac03ba9e6149a5d016bc6709213123152f0cdd7 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Wed, 31 Jan 2024 12:41:00 +0200 Subject: [PATCH 155/161] dotty-cps-async 0.9.20 --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index 3c1f1859..769c11bf 100644 --- a/build.sbt +++ b/build.sbt @@ -11,7 +11,7 @@ val sharedSettings = Seq( scalaVersion := dottyVersion, name := "scala-gopher", //resolvers += "Local Ivy Repository" at "file://"+Path.userHome.absolutePath+"/.ivy2/local", - libraryDependencies += "com.github.rssh" %%% "dotty-cps-async" % "0.9.19", + libraryDependencies += "com.github.rssh" %%% "dotty-cps-async" % "0.9.20", libraryDependencies += "org.scalameta" %%% "munit" % "1.0.0-M10" % Test, ) From 4613b67fbbde3db6650d4c63cac41690b039b29d Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Wed, 31 Jan 2024 12:41:45 +0200 Subject: [PATCH 156/161] sbt-1.9.8 --- project/build.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/build.properties b/project/build.properties index 27430827..abbbce5d 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=1.9.6 +sbt.version=1.9.8 From 1c948c5e85e2091c405a7834062866b99af90c89 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Wed, 31 Jan 2024 12:42:05 +0200 Subject: [PATCH 157/161] 4.0.3 --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index 769c11bf..8d85c7c9 100644 --- a/build.sbt +++ b/build.sbt @@ -3,7 +3,7 @@ val dottyVersion = "3.3.1" //val dottyVersion = "3.1.3-RC1-bin-SNAPSHOT" //val dottyVersion = dottyLatestNightlyBuild.get -ThisBuild/version := "4.0.2" +ThisBuild/version := "4.0.3" ThisBuild/versionScheme := Some("semver-spec") val sharedSettings = Seq( From 481524a5e37540fdad8dc96a9a9bc8aa78d6bb3e Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Tue, 2 Apr 2024 13:45:56 +0300 Subject: [PATCH 158/161] scalajs-1.16.0 --- project/plugins.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/plugins.sbt b/project/plugins.sbt index d3ea172d..e2a1a0da 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -3,6 +3,6 @@ addSbtPlugin("com.typesafe.sbt" % "sbt-site" % "1.4.1") addSbtPlugin("com.typesafe.sbt" % "sbt-ghpages" % "0.6.3") addSbtPlugin("org.portable-scala" % "sbt-scala-native-crossproject" % "1.1.0") addSbtPlugin("org.portable-scala" % "sbt-scalajs-crossproject" % "1.1.0") -addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.15.0") +addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.16.0") addSbtPlugin("org.scala-native" % "sbt-scala-native" % "0.4.14") addSbtPlugin("com.typesafe" % "sbt-mima-plugin" % "1.0.1") From ed33f56be2ab435c8c2accfac0dfed67667591fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=BE=AA?= Date: Mon, 26 Aug 2024 21:37:15 +1200 Subject: [PATCH 159/161] Fix flag in README.md I noticed that the flag isn't rendering correctly on my browser --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4c9fbc33..bcf41272 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# 🇺 🇦 HELP UKRAINE +# 🇺🇦 HELP UKRAINE I'm the creator of this project. My country, Ukraine, [is being invaded by the Russian Federation, right now](https://war.ukraine.ua). If you want to help my country to fight, consider donating to [charity supporting Ukrainian army](https://www.comebackalive.in.ua/). More options is described on [support ukraine](https://supportukrainenow.org/) site. From 216c5eeb192eaeadfa3cd70ee95c85bf9166d0b7 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Thu, 26 Dec 2024 15:02:51 +0200 Subject: [PATCH 160/161] update of dependencies --- build.sbt | 10 +++++----- project/build.properties | 2 +- project/plugins.sbt | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/build.sbt b/build.sbt index 8d85c7c9..43679a5b 100644 --- a/build.sbt +++ b/build.sbt @@ -1,9 +1,9 @@ //val dottyVersion = "3.0.0-RC2-bin-SNAPSHOT" -val dottyVersion = "3.3.1" +val dottyVersion = "3.3.4" //val dottyVersion = "3.1.3-RC1-bin-SNAPSHOT" //val dottyVersion = dottyLatestNightlyBuild.get -ThisBuild/version := "4.0.3" +ThisBuild/version := "4.0.5" ThisBuild/versionScheme := Some("semver-spec") val sharedSettings = Seq( @@ -11,8 +11,8 @@ val sharedSettings = Seq( scalaVersion := dottyVersion, name := "scala-gopher", //resolvers += "Local Ivy Repository" at "file://"+Path.userHome.absolutePath+"/.ivy2/local", - libraryDependencies += "com.github.rssh" %%% "dotty-cps-async" % "0.9.20", - libraryDependencies += "org.scalameta" %%% "munit" % "1.0.0-M10" % Test, + libraryDependencies += "com.github.rssh" %%% "dotty-cps-async" % "0.9.23", + libraryDependencies += "org.scalameta" %%% "munit" % "1.0.0-M12" % Test, ) lazy val root = project @@ -48,7 +48,7 @@ lazy val gopher = crossProject(JSPlatform, JVMPlatform) Compile / doc / scalacOptions := Seq("-groups", "-source-links:shared=github://rssh/scala-gopher/master#shared", "-source-links:jvm=github://rssh/scala-gopher/master#jvm"), - mimaPreviousArtifacts := Set( "com.github.rssh" %% "scala-gopher" % "4.0.0" ) + mimaPreviousArtifacts := Set( "com.github.rssh" %% "scala-gopher" % "4.0.3" ) ).jsSettings( libraryDependencies += ("org.scala-js" %%% "scalajs-java-logging" % "1.0.0").cross(CrossVersion.for3Use2_13), // TODO: switch to ModuleES ? diff --git a/project/build.properties b/project/build.properties index abbbce5d..081fdbbc 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=1.9.8 +sbt.version=1.10.0 diff --git a/project/plugins.sbt b/project/plugins.sbt index e2a1a0da..fa6da8f3 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -3,6 +3,6 @@ addSbtPlugin("com.typesafe.sbt" % "sbt-site" % "1.4.1") addSbtPlugin("com.typesafe.sbt" % "sbt-ghpages" % "0.6.3") addSbtPlugin("org.portable-scala" % "sbt-scala-native-crossproject" % "1.1.0") addSbtPlugin("org.portable-scala" % "sbt-scalajs-crossproject" % "1.1.0") -addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.16.0") -addSbtPlugin("org.scala-native" % "sbt-scala-native" % "0.4.14") +addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.17.0") +addSbtPlugin("org.scala-native" % "sbt-scala-native" % "0.5.6") addSbtPlugin("com.typesafe" % "sbt-mima-plugin" % "1.0.1") From 51f0b1533635f63e22d7f140f0f0e3d015261fd9 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Wed, 7 May 2025 09:21:50 +0300 Subject: [PATCH 161/161] dependency updates --- build.sbt | 10 +++++----- project/plugins.sbt | 8 ++++---- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/build.sbt b/build.sbt index 43679a5b..ce5add4a 100644 --- a/build.sbt +++ b/build.sbt @@ -1,9 +1,9 @@ //val dottyVersion = "3.0.0-RC2-bin-SNAPSHOT" -val dottyVersion = "3.3.4" +val dottyVersion = "3.3.5" //val dottyVersion = "3.1.3-RC1-bin-SNAPSHOT" //val dottyVersion = dottyLatestNightlyBuild.get -ThisBuild/version := "4.0.5" +ThisBuild/version := "4.0.7" ThisBuild/versionScheme := Some("semver-spec") val sharedSettings = Seq( @@ -11,8 +11,8 @@ val sharedSettings = Seq( scalaVersion := dottyVersion, name := "scala-gopher", //resolvers += "Local Ivy Repository" at "file://"+Path.userHome.absolutePath+"/.ivy2/local", - libraryDependencies += "com.github.rssh" %%% "dotty-cps-async" % "0.9.23", - libraryDependencies += "org.scalameta" %%% "munit" % "1.0.0-M12" % Test, + libraryDependencies += "io.github.dotty-cps-async" %%% "dotty-cps-async" % "1.0.2", + libraryDependencies += "org.scalameta" %%% "munit" % "1.0.4" % Test, ) lazy val root = project @@ -48,7 +48,7 @@ lazy val gopher = crossProject(JSPlatform, JVMPlatform) Compile / doc / scalacOptions := Seq("-groups", "-source-links:shared=github://rssh/scala-gopher/master#shared", "-source-links:jvm=github://rssh/scala-gopher/master#jvm"), - mimaPreviousArtifacts := Set( "com.github.rssh" %% "scala-gopher" % "4.0.3" ) + mimaPreviousArtifacts := Set( "com.github.rssh" %% "scala-gopher" % "4.0.5" ) ).jsSettings( libraryDependencies += ("org.scala-js" %%% "scalajs-java-logging" % "1.0.0").cross(CrossVersion.for3Use2_13), // TODO: switch to ModuleES ? diff --git a/project/plugins.sbt b/project/plugins.sbt index fa6da8f3..3c903831 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,8 +1,8 @@ addSbtPlugin("com.jsuereth" % "sbt-pgp" % "2.0.2") addSbtPlugin("com.typesafe.sbt" % "sbt-site" % "1.4.1") addSbtPlugin("com.typesafe.sbt" % "sbt-ghpages" % "0.6.3") -addSbtPlugin("org.portable-scala" % "sbt-scala-native-crossproject" % "1.1.0") -addSbtPlugin("org.portable-scala" % "sbt-scalajs-crossproject" % "1.1.0") -addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.17.0") -addSbtPlugin("org.scala-native" % "sbt-scala-native" % "0.5.6") +addSbtPlugin("org.portable-scala" % "sbt-scala-native-crossproject" % "1.3.2") +addSbtPlugin("org.portable-scala" % "sbt-scalajs-crossproject" % "1.3.2") +addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.19.0") +addSbtPlugin("org.scala-native" % "sbt-scala-native" % "0.5.7") addSbtPlugin("com.typesafe" % "sbt-mima-plugin" % "1.0.1")