├── .travis.yml ├── LICENSE ├── README.md ├── deps.edn ├── doc ├── cljdoc.edn ├── guides │ ├── happy_eyeballs.md │ ├── iterative_queries.md │ └── retry_backoff.md └── tutorials │ ├── hello_flow.md │ ├── hello_task.md │ └── rx_comparison.md ├── java └── missionary │ ├── Cancelled.java │ └── impl │ ├── Ambiguous.java │ ├── Buffer.java │ ├── Continuous.java │ ├── Dataflow.java │ ├── Eduction.java │ ├── Event.java │ ├── Fiber.java │ ├── GroupBy.java │ ├── Heap.java │ ├── Latest.java │ ├── Mailbox.java │ ├── Never.java │ ├── Observe.java │ ├── Propagator.java │ ├── Pub.java │ ├── RaceJoin.java │ ├── Reactor.java │ ├── Reduce.java │ ├── Reductions.java │ ├── Relieve.java │ ├── Rendezvous.java │ ├── Sample.java │ ├── Seed.java │ ├── Semaphore.java │ ├── Sequential.java │ ├── Sleep.java │ ├── Store.java │ ├── Sub.java │ ├── Thunk.java │ ├── Util.java │ ├── Watch.java │ └── Zip.java ├── pom.xml ├── src └── missionary │ ├── Cancelled.js │ ├── core.cljc │ └── impl │ ├── Ambiguous.cljs │ ├── Buffer.cljs │ ├── Continuous.cljs │ ├── Dataflow.cljs │ ├── Eduction.cljs │ ├── Fiber.cljs │ ├── GroupBy.cljs │ ├── Heap.cljs │ ├── Latest.cljs │ ├── Mailbox.cljs │ ├── Never.cljs │ ├── Observe.cljs │ ├── Propagator.cljs │ ├── RaceJoin.cljs │ ├── Reactor.cljs │ ├── Reduce.cljs │ ├── Reductions.cljs │ ├── Relieve.cljs │ ├── Rendezvous.cljs │ ├── Sample.cljs │ ├── Seed.cljs │ ├── Semaphore.cljs │ ├── Sequential.cljs │ ├── Sleep.cljs │ ├── Store.cljs │ ├── Watch.cljs │ └── Zip.cljs ├── tck └── missionary │ ├── PublisherTest.java │ └── SubscriberTest.java └── test ├── lolcat ├── core.cljc └── lib.cljc └── missionary ├── absolve_test.cljc ├── ap_test.cljc ├── attempt_test.cljc ├── buffer_test.cljc ├── compel_test.cljc ├── core_test.cljc ├── cp_test.cljc ├── dfv_test.cljc ├── eduction_test.cljc ├── group_by_test.cljc ├── join_test.cljc ├── latest_test.cljc ├── mbx_test.cljc ├── never_test.cljc ├── none_test.cljc ├── observe_test.cljc ├── propagator_test.cljc ├── pub_test.clj ├── race_test.cljc ├── rdv_test.cljc ├── reduce_test.cljc ├── reductions_test.cljc ├── relieve_test.cljc ├── sample_test.cljc ├── seed_test.cljc ├── sem_test.cljc ├── sp_test.cljc ├── store_test.cljc ├── sub_test.clj ├── tck.cljc ├── test_dev.clj ├── watch_test.cljc └── zip_test.cljc /.travis.yml: -------------------------------------------------------------------------------- 1 | dist: xenial 2 | sudo: true 3 | language: clojure 4 | script: 5 | - mvn compile 6 | - clojure -Aclj-test 7 | - clojure -Acljs-test 8 | - clojure -Acljs-test -x planck 9 | install: 10 | - curl -O https://download.clojure.org/install/linux-install-1.10.1.447.sh 11 | - chmod +x linux-install-1.10.1.447.sh 12 | - sudo ./linux-install-1.10.1.447.sh 13 | - sudo add-apt-repository -y ppa:mfikes/planck 14 | - sudo apt-get update -y 15 | - sudo apt-get install -y planck 16 | -------------------------------------------------------------------------------- /deps.edn: -------------------------------------------------------------------------------- 1 | {:deps 2 | {org.reactivestreams/reactive-streams {:mvn/version "1.0.4"} 3 | cloroutine/cloroutine {:mvn/version "13"}} 4 | 5 | :paths ["src" "target/classes"] 6 | 7 | :aliases 8 | {:tck 9 | {:extra-deps {org.reactivestreams/reactive-streams-tck {:mvn/version "1.0.4"}}} 10 | 11 | :clj-test 12 | {:extra-deps {com.cognitect/test-runner 13 | {:git/url "https://github.com/cognitect-labs/test-runner.git" 14 | :sha "028a6d41ac9ac5d5c405dfc38e4da6b4cc1255d5"}} 15 | :extra-paths ["test"] 16 | :main-opts ["-m" "cognitect.test-runner"]} 17 | 18 | :clj-test-watch {:extra-deps {lambdaisland/kaocha {:mvn/version "1.69.1069"}} 19 | :extra-paths ["test"] 20 | :exec-fn missionary.test-dev/run} 21 | :cljs-test 22 | {:extra-deps {olical/cljs-test-runner {:mvn/version "3.8.0"} 23 | org.clojure/clojurescript {:mvn/version "1.11.60"}} 24 | :extra-paths ["test" "cljs-test-runner-out/gen"] 25 | :main-opts ["-m" "cljs-test-runner.main"]}}} 26 | -------------------------------------------------------------------------------- /doc/cljdoc.edn: -------------------------------------------------------------------------------- 1 | {:cljdoc.doc/tree 2 | [["Readme" {:file "README.md"} 3 | ["Tutorials" {} 4 | ["Hello task" {:file "doc/tutorials/hello_task.md"}] 5 | ["Hello flow" {:file "doc/tutorials/hello_flow.md"}] 6 | ["Comparison to RxJava" {:file "doc/tutorials/rx_comparison.md"}]]]]} 7 | -------------------------------------------------------------------------------- /doc/guides/happy_eyeballs.md: -------------------------------------------------------------------------------- 1 | # Happy eyeballs 2 | 3 | Happy eyeballs ([RFC8305](https://tools.ietf.org/html/rfc8305)) is an algorithm describing the recommended connection strategy for a client attempting to reach a host when the DNS query returns multiple IP addresses. It happens to be notoriously hard to implement with traditional imperative techniques, and recently became a [showcase problem](https://www.youtube.com/watch?v=oLkfnc_UMcE&t=286) for structured concurrency. 4 | 5 | In this guide, we'll implement it with `missionary`. Given a sequence of tasks performing the connection to each endpoint, we want to build a task sequentially performing connection attempts, staggered by given delay (in milliseconds). The first connector is tried, then if it's still pending after staggering delay or when it fails the second connector is tried, and so on until the connector sequence is exhausted. The first successful connection attempt triggers cancellation of other pending connection attempts and makes the whole task terminate with returned socket. If another connection succeeds in the interim, returned sockets must be closed immediately. If every attempt fails, the whole task fails. Cancelling the task triggers cancellation of all pending connection attempts. 6 | ```clojure 7 | (require '[missionary.core :as m]) 8 | ``` 9 | 10 | The core of the algorithm is the `attempt` function, recursively building successive connection attempts. `chosen` is a dataflow variable holding the fastest connection, `delay` is the staggering delay in milliseconds, `close!` is a function closing given socket, `connectors` is a sequence of tasks performing a connection attempt on a single endpoint. 11 | ```clojure 12 | (defn attempt [chosen delay close! connectors] 13 | (if-some [[connector & connectors] connectors] 14 | ;; assigning this dataflow variable will trigger next attempt 15 | (let [trigger (m/dfv)] 16 | (m/race 17 | ;; try to connect to endpoing 18 | (m/sp (try (let [x (m/? connector) 19 | y (chosen x)] 20 | ;; if another attempt succeeded, close socket before terminate 21 | (when-not (identical? x y) (close! x)) y) 22 | (catch Throwable e 23 | ;; if attempt fails early, trigger next attempt immediately 24 | (trigger nil) 25 | (throw e)))) 26 | ;; trigger next attempt if still pending after delay 27 | (m/sp (m/? (m/sleep delay)) 28 | (trigger nil) 29 | (m/? m/never)) 30 | ;; wait for trigger and recursively try next endpoints 31 | (m/sp (m/? trigger) 32 | (m/? (attempt chosen delay close! connectors))))) 33 | ;; no more endpoint, return a failing task 34 | (m/race))) 35 | ``` 36 | 37 | Now, we bind this recursive function to a fresh `chosen` dataflow variable. 38 | ```clojure 39 | (defn happyeyeballs [delay close! connectors] 40 | (m/sp 41 | (try 42 | (m/? (attempt (m/dfv) delay close! connectors)) 43 | (catch Throwable _ 44 | (throw (ex-info "Unable to reach target." 45 | {:delay delay 46 | :close! close! 47 | :connectors connectors})))))) 48 | ``` 49 | 50 | Time to test now. 51 | ```clojure 52 | (defn connector [^java.net.InetAddress addr port] 53 | (m/via m/blk (java.net.Socket. addr (int port)))) 54 | 55 | (defn endpoints [^String host] 56 | (m/via m/blk (java.net.InetAddress/getAllByName host))) 57 | 58 | (defn connectors [^String host port] 59 | (m/sp (map #(connector % port) (m/? (endpoints host))))) 60 | 61 | (defn close! [^java.net.Socket socket] 62 | (.close socket)) 63 | 64 | (defn connect [^String host port delay] 65 | (m/sp (m/? (happyeyeballs delay close! (m/? (connectors host port)))))) 66 | 67 | (m/? (connect "clojure.org" 80 300)) 68 | ``` 69 | -------------------------------------------------------------------------------- /doc/guides/iterative_queries.md: -------------------------------------------------------------------------------- 1 | # Iterative queries 2 | 3 | This guide presents a pattern useful when a dataset must be constructed through a succession of steps, the information produced by each step being required for both streaming and computation of the next step. 4 | 5 | 6 | ## The problem 7 | 8 | We'll work on the problem described in [this clojure issue](https://clojure.atlassian.net/browse/CLJ-1906), involving an API exposing a large collection of items with pagination. The service requires the id of the first requested item, and responds with a bounded-size bundle of successive items. As long as the last item is not reached, the id of the next item is returned such that the service can be iteratively requested to get the whole collection. This behavior is implemented in the following mock API, which is almost copy-pasted from the aforementioned issue, except that we added a delay in the API response to emulate network latency. The result is now asynchronous, so we return it as a task. 9 | 10 | ```clojure 11 | (require '[missionary.core :as m]) 12 | (import '(java.util UUID)) 13 | 14 | (def uuids (repeatedly 10000 #(UUID/randomUUID))) 15 | 16 | (def uuid-index 17 | (loop [uuids uuids 18 | index {}] 19 | (if (seq uuids) 20 | (recur (rest uuids) (assoc index (first uuids) (rest uuids))) 21 | index))) 22 | 23 | (defn api 24 | "pages through uuids, 10 at a time. a list-from of :start starts the listing" 25 | [list-from] 26 | (let [page (take 10 (if (= :start list-from) 27 | uuids (get uuid-index list-from)))] 28 | (m/sleep 10 {:page page :next (last page)}))) 29 | ``` 30 | 31 | The solution suggested in the issue ([refreshed here](https://clojure.atlassian.net/browse/CLJ-2555)) relies on thread blocking and exposes a lazy sequence with a fast reduction path, which is problematic for several reasons. 32 | 33 | Calling the network means errors will happen at some point, and unexpected delays must be expected. Lazy sequences don't memoize errors, and don't expose the thread computing the current step. Once built, the sequence fully controls how and when API calls are performed, there's no way to interrupt a pending call or implement any kind of failure recovery. These mechanisms must be implemented upfront, voiding the benefits of sequence composition. `IReduceInit` doesn't have these problems, but is not composable either (not in practice, at least). 34 | 35 | In addition, this strategy is not an option in threadless environments such as UI toolkits or browsers. 36 | 37 | `missionary` is a better fit for problems of this kind, due to its non-blocking model and its ability to propagate failure and cancellation through composition. 38 | 39 | 40 | ## The antipattern 41 | 42 | The first strategy that comes to mind is to recursively build successive flows for each API call, prepending the current page to the next flow. 43 | 44 | ```clojure 45 | (defn pages 46 | ([] (pages :start)) 47 | ([id] 48 | (m/ap 49 | (let [{:keys [page next]} (m/? (api id)) ;; fetch current page and next id 50 | rest (if (some? next) (pages next) m/none)] ;; recursively build the rest of the flow 51 | (m/?> (m/reductions {} page rest)))))) ;; prepend the page and emit the result 52 | ``` 53 | 54 | This implementation seems to work. However, it will crash in the long run because it builds a hierarchy of processes that grows indefinitely for each successive step. The memory footprint will gradually increase, and the stack will ultimately blow up. 55 | 56 | This is actually the exact same problem as unbounded recursion in synchronous code. The stack works, it's low-level and fast, but the platform doesn't prevent us from abusing it. `missionary` follows the same guideline, it doesn't trade speed for safety and the user is expected to be aware of some pitfalls. This is one of them, just don't do it. 57 | 58 | 59 | ## The solution 60 | 61 | As usual, we can keep stack frames under control using `loop` to make the iteration explicit. A single process represents the whole stream, and each step forks the process in two parts, one emitting a value and the other recursing with the information required to move forward. 62 | 63 | ```clojure 64 | (defn pages 65 | ([] (pages :start)) 66 | ([id] 67 | (m/ap 68 | (loop [id id] 69 | (let [x (->> [:page :next] 70 | (map (m/? (api id))) ;; fetch current page and next id 71 | (remove nil?) ;; detect end of stream 72 | (m/seed) ;; seed branches 73 | (m/?>))] ;; fork the process 74 | (if (uuid? x) (recur x) x)))))) ;; depending on the branch, emit the value or request more 75 | ``` 76 | 77 | ```clojure 78 | (m/? (->> (pages) 79 | (m/eduction (map count)) 80 | (m/reduce +))) 81 | #_=> 10000 82 | ``` 83 | -------------------------------------------------------------------------------- /doc/guides/retry_backoff.md: -------------------------------------------------------------------------------- 1 | # Retry with backoff 2 | 3 | In distributed systems, failures happen : servers crash and reboot, network is unreliable (especially when targeting mobile devices). In many cases it's perfectly acceptable to retry a request after failure, hoping for better conditions. Special care should be taken, however : 4 | * Failures worth retrying are those due to a temporary degradation of technical environment, not those due to business errors (in this case, there's no point retrying). 5 | * Retry rate must be under control, because spamming a server experiencing a burst of load is likely to make the situation even worse. 6 | 7 | In this guide, we'll implement a simple generic backoff retry strategy. 8 | 9 | ```clojure 10 | (require '[missionary.core :as m]) 11 | ``` 12 | 13 | In order to figure out if a failed request is worth retrying, we'll tag the exception info with `:worth-retrying`. The backoff strategy will be a sequence of numbers representing the delays (in milliseconds) of each retry we want to perform before actually propagating the error. 14 | 15 | The test input will look like this : 16 | ```clojure 17 | (def delays ;; Our backoff strategy : 18 | (->> 1000 ;; first retry is delayed by 1 second 19 | (iterate (partial * 2)) ;; exponentially grow delay 20 | (take 5))) ;; give up after 5 retries 21 | 22 | (def request ;; A mock request to exercise the strategy. 23 | (m/sp ;; Failure odds are made pretty high to 24 | (prn :attempt) ;; simulate a terrible connectivity 25 | (if (zero? (rand-int 6)) 26 | :success 27 | (throw (ex-info "failed." {:worth-retrying true}))))) 28 | ``` 29 | 30 | Now, we need a function to bundle this in a task applying given backoff strategy to given task. We define it recursively, the base case being reached when the sequence of retry delays is empty, at which point the request is returned unchanged. 31 | ```clojure 32 | (defn backoff [request delays] 33 | (if-some [[delay & delays] (seq delays)] 34 | (m/sp 35 | (try (m/? request) 36 | (catch Exception e 37 | (if (-> e ex-data :worth-retrying) 38 | (do (m/? (m/sleep delay)) 39 | (m/? (backoff request delays))) 40 | (throw e))))) 41 | request)) 42 | ``` 43 | 44 | Now testing. 45 | ```clojure 46 | (m/? (backoff request delays)) 47 | :attempt 48 | :attempt 49 | :attempt 50 | #=> :success 51 | ``` 52 | -------------------------------------------------------------------------------- /doc/tutorials/hello_flow.md: -------------------------------------------------------------------------------- 1 | # Hello flow 2 | 3 | This tutorial will help you familiarize with the `flow` abstraction. A `flow` is a value representing a process able to produce an arbitrary number of values before terminating. Like `task`s, they're asynchronous under the hood and also support failure and graceful shutdown. 4 | 5 | 6 | ## Basic operations 7 | 8 | Setup a dependency on the latest `missionary` release in your favorite environment and fire up a clojure REPL. Require `missionary`'s single namespace `missionary.core`, which will be aliased to `m` in the following. 9 | ```clojure 10 | (require '[missionary.core :as m]) 11 | ``` 12 | 13 | You can build a flow from an arbitrary collection with `seed`, and you can reduce `flow`s like collections with `reduce`, turning it into a `task`. 14 | ```clojure 15 | ;; A flow producing the 10 first integers 16 | (def input (m/seed (range 10))) 17 | 18 | ;; A task producing the sum of the 10 first integers 19 | (def sum (m/reduce + input)) 20 | 21 | (m/? sum) 22 | #_=> 45 23 | ``` 24 | 25 | `eduction` passes a flow through a transducer. 26 | ```clojure 27 | (m/? (m/reduce conj (m/eduction (partition-all 4) input))) 28 | #_=> [[0 1 2 3] [4 5 6 7] [8 9]] 29 | ``` 30 | 31 | ## Ambiguous evaluation 32 | 33 | Not very interesting so far, because we haven't performed any action yet. Let's introduce the `ap` macro. `ap` is to `flow`s what `sp` is to `task`s. Like `sp`, it can be parked with `?`, but it has an additional superpower : it can be *forked*. 34 | ```clojure 35 | (def hello-world 36 | (m/ap 37 | (println (m/?> (m/seed ["Hello" "World" "!"]))) 38 | (m/? (m/sleep 1000)))) 39 | 40 | (m/? (m/reduce conj hello-world)) 41 | Hello 42 | World 43 | ! 44 | #_=> [nil nil nil] 45 | ``` 46 | 47 | The `?>` operator pulls the first seeded value, forks evaluation and moves on until end of body, producing result `nil`, then *backtracks* evaluation to the fork point, pulls another value, forks evaluation again, and so on until enumeration is exhausted. Meanwhile, `reduce` consolidates each result into a vector. In an `ap` block, expressions have more than one possible value, that's why they're called *ambiguous process*. 48 | 49 | 50 | ## Preemptive forking 51 | 52 | In the previous example, pulling a value from the flow passed to `?>` transfers evaluation control to the forked process, and waits for evaluation to be completed before pulling another value from the flow. In some cases though, we want the flow to keep priority over the forked process, so it can be shutdowned when more values become available. That kind of forking is implemented by `?<`. 53 | 54 | We can use it to implement debounce operators. A debounced flow is a flow emitting only values that are not followed by another one within a given delay. 55 | ```clojure 56 | (import 'missionary.Cancelled) 57 | ``` 58 | 59 | ```clojure 60 | (defn debounce [delay flow] 61 | (m/ap (let [x (m/?< flow)] ;; pull a value preemptively 62 | (try (m/? (m/sleep delay x)) ;; emit this value after given delay 63 | (catch Cancelled _ (m/amb>)))))) ;; emit nothing if cancelled 64 | ``` 65 | 66 | To test it, we need a flow of values emitting at various intervals. 67 | ```clojure 68 | (defn clock [intervals] 69 | (m/ap (let [i (m/?> (m/seed intervals))] 70 | (m/? (m/sleep i i))))) 71 | 72 | (m/? (->> (clock [24 79 67 34 18 9 99 37]) 73 | (debounce 50) 74 | (m/reduce conj))) 75 | #_=> [24 79 9 37] 76 | ``` 77 | 78 | ## Concurrent forking 79 | 80 | What if we want to fork the processes concurrently? Use the `?>` operator with its extra `par` argument. It forks evaluation for `par` values concurrently, *all* values if you use the value `##Inf` for `par`. Values are returned from the flow in the order they finish, which is not necessarily the initial order. 81 | 82 | ```clojure 83 | (m/? (m/reduce conj (m/ap (let [ms (m/?> ##Inf (m/seed [300 100 400 200]))] 84 | (m/? (m/sleep ms ms)))))) 85 | #_=> [100 200 300 400] 86 | ``` 87 | -------------------------------------------------------------------------------- /java/missionary/Cancelled.java: -------------------------------------------------------------------------------- 1 | package missionary; 2 | 3 | public class Cancelled extends Throwable { 4 | public Cancelled() { 5 | this(null); 6 | } 7 | 8 | public Cancelled(String message) { 9 | super(message, null, false, false); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /java/missionary/impl/Buffer.java: -------------------------------------------------------------------------------- 1 | package missionary.impl; 2 | 3 | import clojure.lang.AFn; 4 | import clojure.lang.IDeref; 5 | import clojure.lang.IFn; 6 | 7 | public interface Buffer { 8 | 9 | class Flow extends AFn { 10 | final int capacity; 11 | final IFn input; 12 | 13 | Flow(int c, IFn i) { 14 | capacity = c; 15 | input = i; 16 | } 17 | 18 | @Override 19 | public Object invoke(Object n, Object t) { 20 | Process ps = new Process(); 21 | ps.busy = true; 22 | ps.failed = -1; 23 | ps.buffer = new Object[capacity]; 24 | ps.notifier = (IFn) n; 25 | ps.terminator = (IFn) t; 26 | ps.iterator = input.invoke( 27 | new AFn() { 28 | @Override 29 | public Object invoke() { 30 | IFn cb; 31 | synchronized (ps) { 32 | cb = more(ps); 33 | } 34 | return cb == null ? null : cb.invoke(); 35 | } 36 | }, 37 | new AFn() { 38 | @Override 39 | public Object invoke() { 40 | IFn cb; 41 | synchronized (ps) { 42 | ps.done = true; 43 | cb = more(ps); 44 | } 45 | return cb == null ? null : cb.invoke(); 46 | } 47 | }); 48 | IFn cb; 49 | synchronized (ps) { 50 | cb = more(ps); 51 | } 52 | if (cb != null) cb.invoke(); 53 | return ps; 54 | } 55 | } 56 | 57 | class Process extends AFn implements IDeref { 58 | 59 | static { 60 | Util.printDefault(Process.class); 61 | } 62 | 63 | IFn notifier; 64 | IFn terminator; 65 | Object iterator; 66 | Object[] buffer; 67 | int failed; 68 | int size; 69 | int push; 70 | int pull; 71 | boolean busy; 72 | boolean done; 73 | 74 | @Override 75 | public Object invoke() { 76 | return ((IFn) iterator).invoke(); 77 | } 78 | 79 | @Override 80 | public Object deref() { 81 | return transfer(this); 82 | } 83 | 84 | } 85 | 86 | static Object transfer(Process ps) { 87 | Object[] buffer = ps.buffer; 88 | boolean f; 89 | Object x; 90 | IFn cb; 91 | synchronized (ps) { 92 | int i = ps.pull; 93 | int s = ps.size; 94 | int n = (i + 1) % buffer.length; 95 | f = ps.failed == i; 96 | x = buffer[i]; 97 | buffer[i] = null; 98 | ps.pull = n; 99 | ps.size = s - 1; 100 | cb = s == buffer.length ? more(ps) : null; 101 | cb = s == 1 ? cb : buffer[n] == ps ? ps.terminator : ps.notifier; 102 | } 103 | if (cb != null) cb.invoke(); 104 | return f ? clojure.lang.Util.sneakyThrow((Throwable) x) : x; 105 | } 106 | 107 | static IFn more(Process ps) { 108 | Object[] buffer = ps.buffer; 109 | IFn cb = null; 110 | for(;;) if (ps.busy = !ps.busy) { 111 | int i = ps.push; 112 | int s = ps.size; 113 | ps.push = (i + 1) % buffer.length; 114 | cb = s == 0 ? ps.done ? ps.terminator : ps.notifier : cb; 115 | if (ps.done) buffer[i] = ps; else try { 116 | buffer[i] = ((IDeref) ps.iterator).deref(); 117 | } catch (Throwable e) { 118 | ps.failed = i; 119 | buffer[i] = e; 120 | } 121 | if ((ps.size = s + 1) == buffer.length) break; 122 | } else break; 123 | return cb; 124 | } 125 | 126 | static Flow flow(int capacity, IFn input) { 127 | return new Flow(capacity, input); 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /java/missionary/impl/Dataflow.java: -------------------------------------------------------------------------------- 1 | package missionary.impl; 2 | 3 | import clojure.lang.*; 4 | import missionary.Cancelled; 5 | 6 | import java.util.Iterator; 7 | import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; 8 | 9 | import static missionary.impl.Util.NOP; 10 | 11 | public interface Dataflow { 12 | 13 | AtomicReferenceFieldUpdater STATE = 14 | AtomicReferenceFieldUpdater.newUpdater(Port.class, Object.class, "state"); 15 | 16 | final class Port extends AFn implements Event.Emitter { 17 | static { 18 | Util.printDefault(Port.class); 19 | } 20 | 21 | volatile Object state = null; 22 | 23 | @Override 24 | public Object invoke(Object x) { 25 | return assign(this, x); 26 | } 27 | 28 | @Override 29 | public Object invoke(Object s, Object f) { 30 | return deref(this, (IFn) s, (IFn) f); 31 | } 32 | 33 | @Override 34 | public void cancel(Event e) { 35 | cancelDeref(this, e); 36 | } 37 | } 38 | 39 | static Object assign(Port port, Object x) { 40 | for(;;) { 41 | Object s = port.state; 42 | if (s instanceof Reduced) return ((Reduced) s).deref(); 43 | else if (STATE.compareAndSet(port, s, new Reduced(x))) { 44 | if (s != null) { 45 | Iterator it = RT.iter(s); 46 | do ((Event) it.next()).success.invoke(x); 47 | while (it.hasNext()); 48 | } 49 | return x; 50 | } 51 | } 52 | } 53 | 54 | static IFn deref(Port port, IFn success, IFn failure) { 55 | for(;;) { 56 | Object s = port.state; 57 | if (s instanceof Reduced) { 58 | success.invoke(((Reduced) s).deref()); 59 | return NOP; 60 | } else { 61 | Event e = new Event(port, success, failure); 62 | IPersistentSet set = (s == null) ? PersistentHashSet.EMPTY : (IPersistentSet) s; 63 | if (STATE.compareAndSet(port, s, set.cons(e))) return e; 64 | } 65 | } 66 | } 67 | 68 | static void cancelDeref(Port port, Event e) { 69 | for(;;) { 70 | Object s = port.state; 71 | if (!(s instanceof IPersistentSet)) break; 72 | IPersistentSet set = (IPersistentSet) s; 73 | if (!(set.contains(e))) break; 74 | if (STATE.compareAndSet(port, s, set.count() == 1 ? null : set.disjoin(e))) { 75 | e.failure.invoke(new Cancelled("Dataflow variable derefence cancelled.")); 76 | break; 77 | } 78 | } 79 | } 80 | 81 | static Port make() { 82 | return new Port(); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /java/missionary/impl/Eduction.java: -------------------------------------------------------------------------------- 1 | package missionary.impl; 2 | 3 | import clojure.lang.*; 4 | 5 | import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; 6 | 7 | public interface Eduction { 8 | 9 | AtomicIntegerFieldUpdater PRESSURE = 10 | AtomicIntegerFieldUpdater.newUpdater(Process.class, "pressure"); 11 | 12 | IFn FEED = new AFn() { 13 | @Override 14 | public Object invoke(Object r) { 15 | return r; 16 | } 17 | @Override 18 | public Object invoke(Object r, Object x) { 19 | push((Process) r, x); 20 | return r; 21 | } 22 | }; 23 | 24 | final class Process extends AFn implements IDeref { 25 | 26 | static { 27 | Util.printDefault(Process.class); 28 | } 29 | 30 | IFn reducer; 31 | Object iterator; 32 | IFn notifier; 33 | IFn terminator; 34 | Object[] buffer = new Object[1]; 35 | int offset; 36 | int length; 37 | int error = -1; 38 | boolean done; 39 | 40 | volatile int pressure; 41 | 42 | @Override 43 | public Object invoke() { 44 | cancel(this); 45 | return null; 46 | } 47 | 48 | @Override 49 | public Object deref() { 50 | return transfer(this); 51 | } 52 | } 53 | 54 | static void cancel(Process ps) { 55 | ((IFn) ps.iterator).invoke(); 56 | } 57 | 58 | static Object transfer(Process ps) { 59 | Object x = ps.buffer[ps.offset]; 60 | boolean f = ps.error == ps.offset; 61 | ps.buffer[ps.offset++] = null; 62 | if (ps.offset != ps.length) ps.notifier.invoke(); 63 | else if (0 == PRESSURE.decrementAndGet(ps)) pull(ps); 64 | return f ? clojure.lang.Util.sneakyThrow((Throwable) x) : x; 65 | } 66 | 67 | static void push(Process ps, Object x) { 68 | if (ps.length == ps.buffer.length) { 69 | Object[] bigger = new Object[ps.length << 1]; 70 | System.arraycopy(ps.buffer, 0, bigger, 0, ps.length); 71 | ps.buffer = bigger; 72 | } 73 | ps.buffer[ps.length++] = x; 74 | } 75 | 76 | static void pull(Process ps) { 77 | for(;;) if (ps.done) if (ps.reducer == null) { 78 | ps.terminator.invoke(); 79 | return; 80 | } else { 81 | ps.offset = 0; 82 | ps.length = 0; 83 | try { 84 | ps.reducer.invoke(ps); 85 | } catch (Throwable e) { 86 | ps.error = ps.length; 87 | push(ps, e); 88 | } 89 | ps.reducer = null; 90 | if (ps.length != 0) { 91 | ps.notifier.invoke(); 92 | if (0 != PRESSURE.incrementAndGet(ps)) return; 93 | } 94 | } else if (ps.reducer == null) { 95 | try { 96 | ((IDeref) ps.iterator).deref(); 97 | } catch (Throwable _) {} 98 | if (0 != PRESSURE.decrementAndGet(ps)) return; 99 | } else { 100 | ps.offset = 0; 101 | ps.length = 0; 102 | try { 103 | if (ps.reducer.invoke(ps, ((IDeref) ps.iterator).deref()) instanceof Reduced) { 104 | ps.reducer.invoke(ps); 105 | ps.reducer = null; 106 | cancel(ps); 107 | } 108 | } catch (Throwable e) { 109 | ps.error = ps.length; 110 | push(ps, e); 111 | ps.reducer = null; 112 | cancel(ps); 113 | } 114 | if (ps.length != 0) { 115 | ps.notifier.invoke(); 116 | return; 117 | } 118 | if (0 != PRESSURE.decrementAndGet(ps)) return; 119 | } 120 | } 121 | 122 | static Process run(IFn x, IFn f, IFn n, IFn t) { 123 | Process ps = new Process(); 124 | ps.notifier = n; 125 | ps.terminator = t; 126 | ps.reducer = (IFn) x.invoke(FEED); 127 | ps.iterator = f.invoke( 128 | new AFn() { 129 | @Override 130 | public Object invoke() { 131 | if (0 == PRESSURE.incrementAndGet(ps)) pull(ps); 132 | return null; 133 | } 134 | }, 135 | new AFn() { 136 | @Override 137 | public Object invoke() { 138 | ps.done = true; 139 | if (0 == PRESSURE.incrementAndGet(ps)) pull(ps); 140 | return null; 141 | } 142 | } 143 | ); 144 | if (0 == PRESSURE.decrementAndGet(ps)) pull(ps); 145 | return ps; 146 | } 147 | 148 | } 149 | -------------------------------------------------------------------------------- /java/missionary/impl/Event.java: -------------------------------------------------------------------------------- 1 | package missionary.impl; 2 | 3 | import clojure.lang.AFn; 4 | import clojure.lang.IFn; 5 | 6 | final class Event extends AFn { 7 | interface Emitter { 8 | void cancel(Event e); 9 | } 10 | 11 | Emitter emitter; 12 | IFn success; 13 | IFn failure; 14 | 15 | Event(Emitter e, IFn s, IFn f) { 16 | emitter = e; 17 | success = s; 18 | failure = f; 19 | } 20 | 21 | @Override 22 | public Object invoke() { 23 | emitter.cancel(this); 24 | return null; 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /java/missionary/impl/Fiber.java: -------------------------------------------------------------------------------- 1 | package missionary.impl; 2 | 3 | import clojure.lang.*; 4 | import missionary.Cancelled; 5 | 6 | import java.util.concurrent.CountDownLatch; 7 | 8 | public interface Fiber { 9 | Object park(IFn t); 10 | Object swich(IFn f); 11 | Object fork(Number n, IFn f); 12 | Object check(); 13 | Object unpark(); 14 | 15 | Fiber thread = new Fiber() { 16 | @Override 17 | public Object park(IFn t) { 18 | return new CountDownLatch(1) { 19 | boolean failed; 20 | Object result; 21 | { 22 | IFn cancel = (IFn) t.invoke( 23 | new AFn() { 24 | @Override 25 | public Object invoke(Object x) { 26 | failed = false; 27 | result = x; 28 | countDown(); 29 | return null; 30 | } 31 | }, 32 | new AFn() { 33 | @Override 34 | public Object invoke(Object x) { 35 | failed = true; 36 | result = x; 37 | countDown(); 38 | return null; 39 | } 40 | }); 41 | try {await();} 42 | catch (InterruptedException _) { 43 | cancel.invoke(); 44 | try {await();} catch (InterruptedException __) {} 45 | Thread.currentThread().interrupt(); 46 | } 47 | if (failed) clojure.lang.Util.sneakyThrow((Throwable) result); 48 | } 49 | }.result; 50 | } 51 | 52 | @Override 53 | public Object swich(IFn flow) { 54 | throw new UnsupportedOperationException(); 55 | } 56 | 57 | @Override 58 | public Object fork(Number par, IFn flow) { 59 | throw new UnsupportedOperationException(); 60 | } 61 | 62 | @Override 63 | public Object check() { 64 | return Thread.currentThread().isInterrupted() ? 65 | clojure.lang.Util.sneakyThrow(new Cancelled("Thread interrupted.")) : null; 66 | } 67 | 68 | @Override 69 | public Object unpark() { 70 | throw new UnsupportedOperationException(); 71 | } 72 | }; 73 | 74 | ThreadLocal fiber = ThreadLocal.withInitial(() -> thread); 75 | 76 | static Object current() { 77 | return fiber.get(); 78 | } 79 | 80 | static Object check(Fiber fiber) { 81 | return fiber.check(); 82 | } 83 | 84 | static Object park(Fiber fiber, IFn task) { 85 | return fiber.park(task); 86 | } 87 | 88 | static Object swich(Fiber fiber, IFn flow) { 89 | return fiber.swich(flow); 90 | } 91 | 92 | static Object fork(Fiber fiber, Number par, IFn flow) { 93 | return fiber.fork(par, flow); 94 | } 95 | 96 | static Object unpark(Fiber fiber) { 97 | return fiber.unpark(); 98 | } 99 | } -------------------------------------------------------------------------------- /java/missionary/impl/Heap.java: -------------------------------------------------------------------------------- 1 | package missionary.impl; 2 | 3 | public interface Heap { 4 | 5 | static int[] create(int cap) { 6 | return new int[cap + 1]; 7 | } 8 | 9 | static int size(int[] heap) { 10 | return heap[0]; 11 | } 12 | 13 | static void enqueue(int[] heap, int i) { 14 | int j = heap[0] + 1; 15 | heap[0] = j; 16 | heap[j] = i; 17 | for(;;) if (j == 1) break; else { 18 | int p = j >> 1; 19 | int x = heap[j]; 20 | int y = heap[p]; 21 | if (y < x) break; else { 22 | heap[p] = x; 23 | heap[j] = y; 24 | j = p; 25 | } 26 | } 27 | } 28 | 29 | static int dequeue(int[] heap) { 30 | int s = heap[0]; 31 | int i = heap[1]; 32 | heap[0] = s - 1; 33 | heap[1] = heap[s]; 34 | int j = 1; 35 | for(;;) { 36 | int l = j << 1; 37 | if (l < s) { 38 | int x = heap[j]; 39 | int y = heap[l]; 40 | int r = l + 1; 41 | if (r < s) { 42 | int z = heap[r]; 43 | if (y < z) if (z < x) { 44 | heap[r] = x; 45 | heap[j] = z; 46 | j = r; 47 | } else break; else if (y < x) { 48 | heap[l] = x; 49 | heap[j] = y; 50 | j = l; 51 | } else break; 52 | } else if (y < x) { 53 | heap[l] = x; 54 | heap[j] = y; 55 | j = l; 56 | } else break; 57 | } else break; 58 | } 59 | return i; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /java/missionary/impl/Latest.java: -------------------------------------------------------------------------------- 1 | package missionary.impl; 2 | 3 | import clojure.lang.AFn; 4 | import clojure.lang.IDeref; 5 | import clojure.lang.IFn; 6 | import clojure.lang.RT; 7 | 8 | import java.util.Iterator; 9 | 10 | public interface Latest { 11 | 12 | class Process extends AFn implements IDeref { 13 | 14 | static { 15 | Util.printDefault(Process.class); 16 | } 17 | 18 | IFn combinator; 19 | IFn notifier; 20 | IFn terminator; 21 | Object value; 22 | Object[] args; 23 | Object[] inputs; 24 | int[] dirty; 25 | int alive; 26 | 27 | @Override 28 | public Object invoke() { 29 | kill(this); 30 | return null; 31 | } 32 | 33 | @Override 34 | public Object deref() { 35 | return transfer(this); 36 | } 37 | } 38 | 39 | static void kill(Process ps) { 40 | for (Object it : ps.inputs) 41 | ((IFn) it).invoke(); 42 | } 43 | 44 | static Object transfer(Process ps) { 45 | IFn c = ps.combinator; 46 | Object[] args = ps.args; 47 | Object[] inputs = ps.inputs; 48 | int[] dirty = ps.dirty; 49 | Object x = ps.value; 50 | synchronized (ps) { 51 | try { 52 | ps.value = ps; 53 | if (args == null) throw new Error("Undefined continuous flow."); 54 | for(;;) { 55 | int i = Heap.dequeue(dirty); 56 | Object p = args[i]; 57 | args[i] = ((IDeref) inputs[i]).deref(); 58 | x = x == ps ? x : clojure.lang.Util.equiv(p, args[i]) ? x : ps; 59 | if (0 == Heap.size(dirty)) if (x == ps) { 60 | x = Util.apply(c, args); 61 | if (0 == Heap.size(dirty)) break; 62 | } else break; 63 | } 64 | } catch (Throwable e) { 65 | kill(ps); 66 | while (0 < Heap.size(dirty)) try { 67 | ((IDeref) inputs[Heap.dequeue(dirty)]).deref(); 68 | } catch (Throwable f) {} 69 | ps.notifier = null; 70 | x = e; 71 | } 72 | ps.value = x; 73 | } 74 | if (ps.alive == 0) ps.terminator.invoke(); 75 | return ps.notifier == null ? clojure.lang.Util.sneakyThrow((Throwable) x) : x; 76 | } 77 | 78 | static Process run(IFn c, Object fs, IFn n, IFn t) { 79 | int arity = RT.count(fs); 80 | Iterator it = RT.iter(fs); 81 | Object[] args = new Object[arity]; 82 | Object[] inputs = new Object[arity]; 83 | int[] dirty = Heap.create(arity); 84 | Process ps = new Process(); 85 | ps.notifier = n; 86 | ps.terminator = t; 87 | ps.combinator = c; 88 | ps.value = ps; 89 | ps.alive = arity; 90 | ps.inputs = inputs; 91 | ps.dirty = dirty; 92 | IFn done = new AFn() { 93 | @Override 94 | public Object invoke() { 95 | boolean last; 96 | synchronized (ps) { 97 | last = --ps.alive == 0 && ps.value != ps; 98 | } 99 | if (last) ps.terminator.invoke(); 100 | return null; 101 | } 102 | }; 103 | synchronized (ps) { 104 | for(int i = 0; i < arity; i++) { 105 | int index = i; 106 | inputs[i] = ((IFn) it.next()).invoke(new AFn() { 107 | @Override 108 | public Object invoke() { 109 | boolean race; 110 | synchronized (ps) { 111 | Heap.enqueue(dirty, index); 112 | race = Heap.size(dirty) == 1 && ps.value != ps; 113 | } 114 | if (race) { 115 | IFn n = ps.notifier; 116 | if (n == null) synchronized (ps) { 117 | do try { 118 | ((IDeref) inputs[Heap.dequeue(dirty)]).deref(); 119 | } catch (Throwable f) {} while (0 < Heap.size(dirty)); 120 | } else n.invoke(); 121 | } 122 | return null; 123 | } 124 | }, done); 125 | } 126 | } 127 | if (Heap.size(dirty) == arity) ps.args = args; 128 | n.invoke(); 129 | return ps; 130 | } 131 | } -------------------------------------------------------------------------------- /java/missionary/impl/Mailbox.java: -------------------------------------------------------------------------------- 1 | package missionary.impl; 2 | 3 | import clojure.lang.*; 4 | import missionary.Cancelled; 5 | 6 | import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; 7 | 8 | import static missionary.impl.Util.NOP; 9 | 10 | public interface Mailbox { 11 | 12 | AtomicReferenceFieldUpdater STATE = 13 | AtomicReferenceFieldUpdater.newUpdater(Port.class, Object.class, "state"); 14 | 15 | final class Port extends AFn implements Event.Emitter { 16 | 17 | volatile Object state = null; 18 | 19 | @Override 20 | public Object invoke(Object x) { 21 | post(this, x); 22 | return null; 23 | } 24 | 25 | @Override 26 | public Object invoke(Object s, Object f) { 27 | return fetch(this, (IFn) s, (IFn) f); 28 | } 29 | 30 | @Override 31 | public void cancel(Event e) { 32 | cancelFetch(this, e); 33 | } 34 | } 35 | static void post(Port port, Object x) { 36 | for(;;) { 37 | Object s = port.state; 38 | if (s instanceof IPersistentSet) { 39 | Event e = (Event) RT.iter(s).next(); 40 | IPersistentSet set = (IPersistentSet) s; 41 | if (STATE.compareAndSet(port, s, set.count() == 1 ? null : set.disjoin(e))) { 42 | e.success.invoke(x); 43 | break; 44 | } 45 | } else if (STATE.compareAndSet(port, s, s == null ? 46 | PersistentVector.create(x) : ((IPersistentVector) s).cons(x))) break; 47 | } 48 | } 49 | 50 | static Object fetch(Port port, IFn success, IFn failure) { 51 | for(;;) { 52 | Object s = port.state; 53 | if (s instanceof IPersistentVector) { 54 | IPersistentVector v = (IPersistentVector) s; 55 | int n = v.count(); 56 | if (STATE.compareAndSet(port, s, n == 1 ? null : 57 | new APersistentVector.SubVector(null, v, 1, n))) { 58 | success.invoke(v.nth(0)); 59 | return NOP; 60 | } 61 | } else { 62 | Event e = new Event(port, success, failure); 63 | IPersistentSet set = (s == null) ? PersistentHashSet.EMPTY : (IPersistentSet) s; 64 | if (STATE.compareAndSet(port, s, set.cons(e))) return e; 65 | } 66 | } 67 | } 68 | 69 | static void cancelFetch(Port port, Event e) { 70 | for(;;) { 71 | Object s = port.state; 72 | if (!(s instanceof IPersistentSet)) break; 73 | IPersistentSet set = (IPersistentSet) s; 74 | if (!(set.contains(e))) break; 75 | if (STATE.compareAndSet(port, s, set.count() == 1 ? null : set.disjoin(e))) { 76 | e.failure.invoke(new Cancelled("Mailbox fetch cancelled.")); 77 | break; 78 | } 79 | } 80 | } 81 | 82 | static Port make() { 83 | return new Port(); 84 | } 85 | } 86 | 87 | -------------------------------------------------------------------------------- /java/missionary/impl/Never.java: -------------------------------------------------------------------------------- 1 | package missionary.impl; 2 | 3 | import clojure.lang.*; 4 | import missionary.Cancelled; 5 | 6 | import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; 7 | 8 | public interface Never { 9 | AtomicReferenceFieldUpdater FAILURE = 10 | AtomicReferenceFieldUpdater.newUpdater(Process.class, IFn.class, "failure"); 11 | 12 | final class Process extends AFn { 13 | static { 14 | Util.printDefault(Process.class); 15 | } 16 | 17 | volatile IFn failure; 18 | 19 | @Override 20 | public Object invoke() { 21 | cancel(this); 22 | return null; 23 | } 24 | } 25 | 26 | static void cancel(Process ps) { 27 | IFn f = ps.failure; 28 | if (f != null && FAILURE.compareAndSet(ps, f, null)) 29 | f.invoke(new Cancelled("Never cancelled.")); 30 | } 31 | 32 | static Process run(IFn f) { 33 | Process ps = new Process(); 34 | ps.failure = f; 35 | return ps; 36 | } 37 | 38 | } -------------------------------------------------------------------------------- /java/missionary/impl/Observe.java: -------------------------------------------------------------------------------- 1 | package missionary.impl; 2 | 3 | import clojure.lang.AFn; 4 | import clojure.lang.IDeref; 5 | import clojure.lang.IFn; 6 | import missionary.Cancelled; 7 | 8 | public interface Observe { 9 | 10 | class Process extends AFn implements IDeref { 11 | 12 | static { 13 | Util.printDefault(Process.class); 14 | } 15 | 16 | IFn notifier; 17 | IFn terminator; 18 | Object unsub; 19 | Object value; 20 | 21 | @Override 22 | public Object invoke() { 23 | kill(this); 24 | return null; 25 | } 26 | 27 | @Override 28 | public Object deref() { 29 | return transfer(this); 30 | } 31 | } 32 | 33 | static void kill(Process ps) { 34 | IFn cb; 35 | synchronized (ps) { 36 | cb = ps.notifier; 37 | if (cb != null) { 38 | ps.notifier = null; 39 | try { 40 | ((IFn) ps.unsub).invoke(); 41 | ps.unsub = new Cancelled("Observe cancelled."); 42 | } catch (Throwable e) { 43 | ps.unsub = e; 44 | } 45 | if (ps.value != ps) { 46 | ps.value = ps; 47 | ps.notifyAll(); 48 | cb = null; 49 | } 50 | } 51 | } 52 | if (cb != null) cb.invoke(); 53 | } 54 | 55 | static Object transfer(Process ps) { 56 | if (ps.notifier == null) { 57 | ps.terminator.invoke(); 58 | return clojure.lang.Util.sneakyThrow((Throwable) ps.unsub); 59 | } else synchronized (ps) { 60 | Object x = ps.value; 61 | ps.value = ps; 62 | ps.notify(); 63 | return x; 64 | } 65 | } 66 | 67 | static Object run(IFn sub, IFn n, IFn t) { 68 | Process ps = new Process(); 69 | ps.notifier = n; 70 | ps.terminator = t; 71 | ps.value = ps; 72 | try { 73 | ps.unsub = sub.invoke(new AFn() { 74 | @Override 75 | public Object invoke(Object x) { 76 | IFn cb; 77 | synchronized (ps) { 78 | while (ps.value != ps) try { 79 | ps.wait(); 80 | } catch (InterruptedException e) { 81 | clojure.lang.Util.sneakyThrow(e); 82 | } 83 | cb = ps.notifier; 84 | if (cb != null) ps.value = x; 85 | } 86 | return cb == null ? null : cb.invoke(); 87 | } 88 | }); 89 | } catch (Throwable e) { 90 | IFn cb = n; 91 | synchronized (ps) { 92 | ps.unsub = e; 93 | ps.notifier = null; 94 | if (ps.value != ps) { 95 | ps.value = ps; 96 | cb = null; 97 | } 98 | } 99 | if (cb != null) cb.invoke(); 100 | } 101 | return ps; 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /java/missionary/impl/Pub.java: -------------------------------------------------------------------------------- 1 | package missionary.impl; 2 | 3 | import clojure.lang.AFn; 4 | import clojure.lang.IDeref; 5 | import clojure.lang.IFn; 6 | import org.reactivestreams.Subscriber; 7 | import org.reactivestreams.Subscription; 8 | 9 | public interface Pub { 10 | class Process implements Subscription { 11 | static { 12 | Util.printDefault(Process.class); 13 | } 14 | 15 | Subscriber subscriber; 16 | Object iterator; 17 | Object current; 18 | long requested; 19 | boolean busy; 20 | boolean done; 21 | 22 | @Override 23 | public void request(long n) { 24 | if (0 < n) more(this, n); 25 | else kill(this, new IllegalArgumentException("Negative subscription request (3.9)")); 26 | } 27 | 28 | @Override 29 | public void cancel() { 30 | kill(this, null); 31 | } 32 | 33 | } 34 | 35 | static void kill(Process ps, Throwable e) { 36 | long requested; 37 | Subscriber sub; 38 | synchronized (ps) { 39 | requested = ps.requested; 40 | sub = ps.subscriber; 41 | ps.subscriber = null; 42 | if (requested < 0) ps.current = null; 43 | } 44 | if (sub != null) { 45 | ((IFn) ps.iterator).invoke(); 46 | if (requested < 0) ready(ps); 47 | if (e != null) sub.onError(e); 48 | } 49 | } 50 | 51 | static void more(Process ps, long n) { 52 | long requested; 53 | Object current; 54 | Subscriber sub; 55 | synchronized (ps) { 56 | sub = ps.subscriber; 57 | current = ps.current; 58 | requested = ps.requested; 59 | long r = requested + n; 60 | ps.requested = r < 0 ? Long.MAX_VALUE : r; 61 | if (sub != null && requested < 0) ps.current = null; 62 | } 63 | if (sub != null && requested < 0) { 64 | sub.onNext(current); 65 | ready(ps); 66 | } 67 | } 68 | 69 | static void ready(Process ps) { 70 | Subscriber sub; 71 | for(;;) { 72 | boolean terminated = false; 73 | Object current = null; 74 | synchronized (ps) { 75 | sub = ps.subscriber; 76 | if (ps.busy = !ps.busy) if (sub == null) if (ps.done); 77 | else Util.discard(ps.iterator); 78 | else if (ps.done) { 79 | terminated = true; 80 | current = ps.current; 81 | ps.current = null; 82 | ps.subscriber = null; 83 | } else try { 84 | current = ((IDeref) ps.iterator).deref(); 85 | if (0 == ps.requested--) { 86 | ps.current = current; 87 | break; 88 | } 89 | } catch (Throwable e) { 90 | ps.requested = Long.MAX_VALUE; 91 | ps.current = e; 92 | } else break; 93 | } 94 | if (terminated) if (current == null) sub.onComplete(); 95 | else sub.onError((Throwable) current); 96 | else if (current == null); 97 | else sub.onNext(current); 98 | } 99 | } 100 | 101 | static void run(IFn f, Subscriber s) { 102 | Process ps = new Process(); 103 | ps.busy = true; 104 | ps.subscriber = s; 105 | ps.iterator = f.invoke(new AFn() { 106 | @Override 107 | public Object invoke() { 108 | ready(ps); 109 | return null; 110 | } 111 | }, new AFn() { 112 | @Override 113 | public Object invoke() { 114 | ps.done = true; 115 | ready(ps); 116 | return null; 117 | } 118 | }); 119 | s.onSubscribe(ps); 120 | ready(ps); 121 | } 122 | 123 | } 124 | -------------------------------------------------------------------------------- /java/missionary/impl/RaceJoin.java: -------------------------------------------------------------------------------- 1 | package missionary.impl; 2 | 3 | import clojure.lang.AFn; 4 | import clojure.lang.IFn; 5 | import clojure.lang.RT; 6 | 7 | import java.util.Iterator; 8 | import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; 9 | 10 | public interface RaceJoin { 11 | 12 | AtomicIntegerFieldUpdater RACE = 13 | AtomicIntegerFieldUpdater.newUpdater(Process.class, "race"); 14 | AtomicIntegerFieldUpdater JOIN = 15 | AtomicIntegerFieldUpdater.newUpdater(Process.class, "join"); 16 | 17 | final class Process extends AFn { 18 | static { 19 | Util.printDefault(Process.class); 20 | } 21 | 22 | IFn raceCallback; 23 | IFn joinCallback; 24 | IFn combinator; 25 | 26 | Object[] children; 27 | Object[] result; 28 | 29 | volatile int race = -2; 30 | volatile int join = 0; 31 | 32 | @Override 33 | public Object invoke() { 34 | cancel(this); 35 | return null; 36 | } 37 | } 38 | 39 | static void cancel(Process ps) { 40 | for(Object c : ps.children) { 41 | ((IFn) c).invoke(); 42 | } 43 | } 44 | 45 | static void terminated(Process ps) { 46 | if (ps.result.length == JOIN.incrementAndGet(ps)) { 47 | if (ps.race < 0) try { 48 | ps.joinCallback.invoke(Util.apply(ps.combinator, ps.result)); 49 | } catch (Throwable e) { 50 | ps.raceCallback.invoke(e); 51 | } else ps.raceCallback.invoke(ps.result[ps.race]); 52 | } 53 | } 54 | 55 | static Process run(boolean r, IFn c, Object tasks, IFn s, IFn f) { 56 | Process ps = new Process(); 57 | ps.raceCallback = r ? s : f; 58 | ps.joinCallback = r ? f : s; 59 | ps.combinator = c; 60 | Iterator it = RT.iter(tasks); 61 | int count = RT.count(tasks); 62 | ps.children = new Object[count]; 63 | ps.result = new Object[count]; 64 | int i = 0; 65 | do { 66 | int index = i++; 67 | IFn joinCallback = new AFn() { 68 | @Override 69 | public Object invoke(Object x) { 70 | ps.result[index] = x; 71 | terminated(ps); 72 | return null; 73 | } 74 | }; 75 | IFn raceCallback = new AFn() { 76 | @Override 77 | public Object invoke(Object x) { 78 | for(;;) { 79 | int r = ps.race; 80 | if (0 <= r) break; 81 | if (RACE.compareAndSet(ps, r, index)) { 82 | if (r == -1) cancel(ps); 83 | break; 84 | } 85 | } 86 | return joinCallback.invoke(x); 87 | } 88 | }; 89 | ps.children[index] = ((IFn) it.next()).invoke( 90 | r ? raceCallback : joinCallback, 91 | r ? joinCallback : raceCallback); 92 | } while (it.hasNext()); 93 | if (!RACE.compareAndSet(ps, -2, -1)) cancel(ps); 94 | return ps; 95 | } 96 | 97 | } -------------------------------------------------------------------------------- /java/missionary/impl/Reduce.java: -------------------------------------------------------------------------------- 1 | package missionary.impl; 2 | 3 | import clojure.lang.AFn; 4 | import clojure.lang.IDeref; 5 | import clojure.lang.IFn; 6 | import clojure.lang.Reduced; 7 | 8 | public interface Reduce { 9 | 10 | class Process extends AFn { 11 | static { 12 | Util.printDefault(Process.class); 13 | } 14 | 15 | IFn reducer; 16 | IFn status; 17 | IFn failure; 18 | Object result; 19 | Object input; 20 | boolean done; 21 | boolean busy; 22 | 23 | @Override 24 | public Object invoke() { 25 | return ((IFn) input).invoke(); 26 | } 27 | } 28 | 29 | static void transfer(Process p) { 30 | IFn f = p.reducer; 31 | Object r = p.result; 32 | try { 33 | r = r == p ? f.invoke() : f.invoke(r, ((IDeref) p.input).deref()); 34 | if (r instanceof Reduced) { 35 | ((IFn) p.input).invoke(); 36 | p.reducer = null; 37 | r = ((Reduced) r).deref(); 38 | } 39 | } catch (Throwable e) { 40 | ((IFn) p.input).invoke(); 41 | p.reducer = null; 42 | p.status = p.failure; 43 | r = e; 44 | } 45 | p.result = r; 46 | } 47 | 48 | static void ready(Process p) { 49 | while (p.busy = !p.busy) if (p.done) { 50 | p.status.invoke(p.result); 51 | break; 52 | } else if (p.reducer == null) try { 53 | ((IDeref) p.input).deref(); 54 | } catch (Throwable e) { 55 | } else transfer(p); 56 | } 57 | 58 | static Process run(IFn r, IFn i, IFn s, IFn f) { 59 | Process p = new Process(); 60 | p.busy = true; 61 | p.result = p; 62 | p.status = s; 63 | p.failure = f; 64 | p.reducer = r; 65 | p.input = i.invoke(new AFn() { 66 | @Override 67 | public Object invoke() { 68 | synchronized (p) { 69 | ready(p); 70 | return null; 71 | } 72 | } 73 | }, new AFn() { 74 | @Override 75 | public Object invoke() { 76 | synchronized (p) { 77 | p.done = true; 78 | ready(p); 79 | return null; 80 | } 81 | } 82 | }); 83 | synchronized (p) { 84 | transfer(p); 85 | ready(p); 86 | } 87 | return p; 88 | } 89 | 90 | } 91 | -------------------------------------------------------------------------------- /java/missionary/impl/Reductions.java: -------------------------------------------------------------------------------- 1 | package missionary.impl; 2 | 3 | import clojure.lang.AFn; 4 | import clojure.lang.IDeref; 5 | import clojure.lang.IFn; 6 | import clojure.lang.Reduced; 7 | 8 | public interface Reductions { 9 | 10 | class Process extends AFn implements IDeref { 11 | static { 12 | Util.printDefault(Process.class); 13 | } 14 | 15 | Object result; 16 | IFn reducer; 17 | IFn notifier; 18 | IFn terminator; 19 | Object input; 20 | boolean done; 21 | boolean busy; 22 | 23 | @Override 24 | public Object invoke() { 25 | return ((IFn) input).invoke(); 26 | } 27 | 28 | @Override 29 | public Object deref() { 30 | return transfer(this); 31 | } 32 | } 33 | 34 | static Object transfer(Process ps) { 35 | IFn cb; 36 | synchronized (ps) { 37 | try { 38 | IFn f = ps.reducer; 39 | Object r = ps.result; 40 | r = r == ps ? f.invoke() : f.invoke(r, ((IDeref) ps.input).deref()); 41 | if (r instanceof Reduced) { 42 | ((IFn) ps.input).invoke(); 43 | ps.reducer = null; 44 | r = ((Reduced) r).deref(); 45 | } 46 | ps.result = r; 47 | } catch (Throwable e) { 48 | ((IFn) ps.input).invoke(); 49 | ps.notifier = null; 50 | ps.reducer = null; 51 | ps.result = e; 52 | } 53 | cb = ready(ps); 54 | } 55 | if (cb != null) cb.invoke(); 56 | return ps.notifier == null ? clojure.lang.Util.sneakyThrow((Throwable) ps.result) : ps.result; 57 | } 58 | 59 | static IFn ready(Process ps) { 60 | IFn cb = null; 61 | while (ps.busy = !ps.busy) if (ps.done) { 62 | cb = ps.terminator; 63 | break; 64 | } else if (ps.reducer == null) try { 65 | ((IDeref) ps.input).deref(); 66 | } catch (Throwable ignored) { 67 | } else { 68 | cb = ps.notifier; 69 | break; 70 | } 71 | return cb; 72 | } 73 | 74 | static Process run(IFn r, IFn f, IFn n, IFn t) { 75 | Process ps = new Process(); 76 | ps.busy = true; 77 | ps.result = ps; 78 | ps.reducer = r; 79 | ps.notifier = n; 80 | ps.terminator = t; 81 | ps.input = f.invoke( 82 | new AFn() { 83 | @Override 84 | public Object invoke() { 85 | IFn cb; 86 | synchronized (ps) { 87 | cb = ready(ps); 88 | } 89 | return cb == null ? null : cb.invoke(); 90 | } 91 | }, 92 | new AFn() { 93 | @Override 94 | public Object invoke() { 95 | ps.done = true; 96 | IFn cb; 97 | synchronized (ps) { 98 | cb = ready(ps); 99 | } 100 | return cb == null ? null : cb.invoke(); 101 | }}); 102 | n.invoke(); 103 | return ps; 104 | } 105 | 106 | } 107 | -------------------------------------------------------------------------------- /java/missionary/impl/Relieve.java: -------------------------------------------------------------------------------- 1 | package missionary.impl; 2 | 3 | import clojure.lang.AFn; 4 | import clojure.lang.IDeref; 5 | import clojure.lang.IFn; 6 | 7 | public interface Relieve { 8 | 9 | class Process extends AFn implements IDeref { 10 | static { 11 | Util.printDefault(Process.class); 12 | } 13 | 14 | IFn reducer; 15 | IFn notifier; 16 | IFn terminator; 17 | Object iterator; 18 | Object current; 19 | boolean busy; 20 | boolean done; 21 | 22 | @Override 23 | public Object invoke() { 24 | return ((IFn) iterator).invoke(); 25 | } 26 | 27 | @Override 28 | public Object deref() { 29 | return transfer(this); 30 | } 31 | } 32 | 33 | static Object transfer(Process ps) { 34 | Object x; 35 | IFn n, r; 36 | synchronized (ps) { 37 | n = ps.notifier; 38 | r = ps.reducer; 39 | x = ps.current; 40 | ps.current = ps; 41 | } 42 | if (r == null) ps.terminator.invoke(); 43 | return n == null ? clojure.lang.Util.sneakyThrow((Throwable) x) : x; 44 | } 45 | 46 | static IFn ready(Process ps) { 47 | IFn cb = null; 48 | while (ps.busy = !ps.busy) if (ps.done) { 49 | ps.reducer = null; 50 | if (ps.current == ps) cb = ps.terminator; 51 | } else { 52 | IFn n = ps.notifier; 53 | if (n == null) Util.discard(ps.iterator); else { 54 | Object r = ps.current; 55 | try { 56 | Object x = ((IDeref) ps.iterator).deref(); 57 | ps.current = r == ps ? x : ps.reducer.invoke(r, x); 58 | } catch (Throwable e) { 59 | ps.current = e; 60 | ps.notifier = null; 61 | ((IFn) ps.iterator).invoke(); 62 | } 63 | if (r == ps) cb = n; 64 | } 65 | } 66 | return cb; 67 | } 68 | 69 | static Process run(IFn r, IFn f, IFn n, IFn t) { 70 | IFn cb; 71 | Process ps = new Process(); 72 | synchronized (ps) { 73 | ps.busy = true; 74 | ps.reducer = r; 75 | ps.notifier = n; 76 | ps.terminator = t; 77 | ps.current = ps; 78 | ps.iterator = f.invoke( 79 | new AFn() { 80 | @Override 81 | public Object invoke() { 82 | IFn cb; 83 | synchronized (ps) { 84 | cb = ready(ps); 85 | } 86 | if (cb != null) cb.invoke(); 87 | return null; 88 | }}, 89 | new AFn() { 90 | @Override 91 | public Object invoke() { 92 | ps.done = true; 93 | IFn cb; 94 | synchronized (ps) { 95 | cb = ready(ps); 96 | } 97 | if (cb != null) cb.invoke(); 98 | return null; 99 | }}); 100 | cb = ready(ps); 101 | } 102 | if (cb != null) cb.invoke(); 103 | return ps; 104 | } 105 | 106 | } 107 | -------------------------------------------------------------------------------- /java/missionary/impl/Rendezvous.java: -------------------------------------------------------------------------------- 1 | package missionary.impl; 2 | 3 | import clojure.lang.*; 4 | import missionary.Cancelled; 5 | 6 | import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; 7 | 8 | import static missionary.impl.Util.NOP; 9 | 10 | public interface Rendezvous { 11 | 12 | AtomicReferenceFieldUpdater STATE = 13 | AtomicReferenceFieldUpdater.newUpdater(Port.class, Object.class, "state"); 14 | 15 | final class Port extends AFn implements Event.Emitter { 16 | static { 17 | Util.printDefault(Port.class); 18 | } 19 | 20 | volatile Object state = null; 21 | 22 | @Override 23 | public Object invoke(Object x) { 24 | return new Give(this, x); 25 | } 26 | 27 | @Override 28 | public Object invoke(Object s, Object f) { 29 | return take(this, (IFn) s, (IFn) f); 30 | } 31 | 32 | @Override 33 | public void cancel(Event e) { 34 | cancelTake(this, e); 35 | } 36 | } 37 | 38 | final class Give extends AFn implements Event.Emitter { 39 | static { 40 | Util.printDefault(Give.class); 41 | } 42 | 43 | final Port port; 44 | final Object value; 45 | 46 | Give(Port p, Object x) { 47 | port = p; 48 | value = x; 49 | } 50 | 51 | @Override 52 | public Object invoke(Object s, Object f) { 53 | return give(this, (IFn) s, (IFn) f); 54 | } 55 | 56 | @Override 57 | public void cancel(Event e) { 58 | cancelGive(this, e); 59 | } 60 | } 61 | 62 | static void cancelTake(Port port, Event event) { 63 | for(;;) { 64 | Object s = port.state; 65 | if (!(s instanceof IPersistentSet)) break; 66 | IPersistentSet set = (IPersistentSet) s; 67 | if (!(set.contains(event))) break; 68 | if (STATE.compareAndSet(port, s, set.count() == 1 ? null : set.disjoin(event))) { 69 | event.failure.invoke(new Cancelled("Rendez-vous take cancelled.")); 70 | break; 71 | } 72 | } 73 | } 74 | 75 | static IFn take(Port port, IFn success, IFn failure) { 76 | for(;;) { 77 | Object s = port.state; 78 | if (s instanceof IPersistentMap) { 79 | IPersistentMap map = (IPersistentMap) s; 80 | MapEntry e = (MapEntry) RT.iter(s).next(); 81 | if (STATE.compareAndSet(port, s, map.count() == 1 ? null : map.without(e.key()))) { 82 | ((Event) e.key()).success.invoke(null); 83 | success.invoke(e.val()); 84 | return NOP; 85 | } 86 | } else { 87 | Event e = new Event(port, success, failure); 88 | IPersistentSet set = (s == null) ? PersistentHashSet.EMPTY : (IPersistentSet) s; 89 | if (STATE.compareAndSet(port, s, set.cons(e))) return e; 90 | } 91 | } 92 | } 93 | 94 | static void cancelGive(Give g, Event e) { 95 | Port port = g.port; 96 | for(;;) { 97 | Object s = port.state; 98 | if (!(s instanceof IPersistentMap)) break; 99 | IPersistentMap map = (IPersistentMap) s; 100 | if (!(map.containsKey(e))) break; 101 | if (STATE.compareAndSet(port, s, map.count() == 1 ? null : map.without(e))) { 102 | e.failure.invoke(new Cancelled("Rendez-vous give cancelled.")); 103 | break; 104 | } 105 | } 106 | } 107 | 108 | static IFn give(Give g, IFn success, IFn failure) { 109 | Port port = g.port; 110 | Object value = g.value; 111 | for(;;) { 112 | Object s = port.state; 113 | if (s instanceof IPersistentSet) { 114 | IPersistentSet set = (IPersistentSet) s; 115 | Event e = (Event) RT.iter(s).next(); 116 | if (STATE.compareAndSet(port, s, set.count() == 1 ? null : set.disjoin(e))) { 117 | e.success.invoke(value); 118 | success.invoke(null); 119 | return NOP; 120 | } 121 | } else { 122 | Event e = new Event(g, success, failure); 123 | IPersistentMap map = s == null ? PersistentHashMap.EMPTY : (IPersistentMap) s; 124 | if (STATE.compareAndSet(port, s, map.assoc(e, value))) return e; 125 | } 126 | } 127 | } 128 | 129 | static Port make() { 130 | return new Port(); 131 | } 132 | } -------------------------------------------------------------------------------- /java/missionary/impl/Sample.java: -------------------------------------------------------------------------------- 1 | package missionary.impl; 2 | 3 | import clojure.lang.AFn; 4 | import clojure.lang.IDeref; 5 | import clojure.lang.IFn; 6 | import clojure.lang.RT; 7 | 8 | import java.util.Iterator; 9 | 10 | public interface Sample { 11 | 12 | class Process extends AFn implements IDeref { 13 | 14 | static { 15 | Util.printDefault(Process.class); 16 | } 17 | 18 | IFn combinator; 19 | IFn notifier; 20 | IFn terminator; 21 | Object[] args; 22 | Object[] inputs; 23 | boolean busy; 24 | boolean done; 25 | int alive; 26 | 27 | @Override 28 | public Object invoke() { 29 | for (Object input : inputs) ((IFn) input).invoke(); 30 | return null; 31 | } 32 | 33 | @Override 34 | public Object deref() { 35 | return transfer(this); 36 | } 37 | } 38 | 39 | static IFn ready(Process ps) { 40 | IFn cb = null; 41 | Object[] args = ps.args; 42 | Object[] inputs = ps.inputs; 43 | int sampled = inputs.length - 1; 44 | while (ps.busy = !ps.busy) if (ps.done) { 45 | for (int i = 0; i != sampled; i++) { 46 | Object input = inputs[i]; 47 | ((IFn) input).invoke(); 48 | if (args[i] == args) Util.discard(input); 49 | else args[i] = args; 50 | } 51 | cb = --ps.alive == 0 ? ps.terminator : null; 52 | break; 53 | } else if (args[sampled] == args) Util.discard(inputs[sampled]); else { 54 | cb = ps.notifier; 55 | break; 56 | } 57 | return cb; 58 | } 59 | 60 | static Object transfer(Process ps) { 61 | IFn cb; 62 | Object x; 63 | IFn c = ps.combinator; 64 | Object[] args = ps.args; 65 | Object[] inputs = ps.inputs; 66 | int sampled = inputs.length - 1; 67 | Object sampler = inputs[sampled]; 68 | synchronized (ps) { 69 | try { 70 | try { 71 | if (c == null) throw new Error("Undefined continuous flow."); 72 | for (int i = 0; i != sampled; i++) if (args[i] == args) { 73 | IDeref input = (IDeref) inputs[i]; 74 | do { 75 | args[i] = null; 76 | x = input.deref(); 77 | } while (args[i] == args); 78 | args[i] = x; 79 | } 80 | } catch (Throwable e) { 81 | Util.discard(sampler); 82 | throw e; 83 | } 84 | args[sampled] = ((IDeref) sampler).deref(); 85 | x = Util.apply(c, args); 86 | } catch (Throwable e) { 87 | x = e; 88 | ps.notifier = null; 89 | ((IFn) sampler).invoke(); 90 | args[sampled] = args; 91 | } 92 | cb = ready(ps); 93 | } 94 | if (cb != null) cb.invoke(); 95 | return ps.notifier == null ? clojure.lang.Util.sneakyThrow((Throwable) x) : x; 96 | } 97 | 98 | static Object run(Object c, Object f, Object fs, Object n, Object t) { 99 | int arity = RT.count(fs) + 1; 100 | Object[] inputs = new Object[arity]; 101 | Object[] args = new Object[arity]; 102 | Process ps = new Process(); 103 | ps.combinator = (IFn) c; 104 | ps.notifier = (IFn) n; 105 | ps.terminator = (IFn) t; 106 | ps.inputs = inputs; 107 | ps.args = args; 108 | ps.alive = arity; 109 | IFn done = new AFn() { 110 | @Override 111 | public Object invoke() { 112 | boolean last; 113 | synchronized (ps) { 114 | last = --ps.alive == 0; 115 | } 116 | return last ? ps.terminator.invoke() : null; 117 | } 118 | }; 119 | synchronized (ps) { 120 | int i = 0; 121 | IFn prev = (IFn) f; 122 | Iterator flows = RT.iter(fs); 123 | while (flows.hasNext()) { 124 | int index = i; 125 | inputs[i] = prev.invoke(new AFn() { 126 | @Override 127 | public Object invoke() { 128 | Object[] args = ps.args; 129 | synchronized (ps) { 130 | if (args[index] == args) Util.discard(ps.inputs[index]); 131 | else args[index] = args; 132 | } 133 | return null; 134 | } 135 | }, done); 136 | if (args[i] == null) ps.combinator = null; 137 | prev = (IFn) flows.next(); 138 | i++; 139 | } 140 | inputs[i] = prev.invoke(new AFn() { 141 | @Override 142 | public Object invoke() { 143 | IFn cb; 144 | synchronized (ps) { 145 | cb = ready(ps); 146 | } 147 | return cb == null ? null : cb.invoke(); 148 | } 149 | }, new AFn() { 150 | @Override 151 | public Object invoke() { 152 | ps.done = true; 153 | IFn cb; 154 | synchronized (ps) { 155 | cb = ready(ps); 156 | } 157 | return cb == null ? null : cb.invoke(); 158 | } 159 | }); 160 | } 161 | return ps; 162 | } 163 | 164 | } -------------------------------------------------------------------------------- /java/missionary/impl/Seed.java: -------------------------------------------------------------------------------- 1 | package missionary.impl; 2 | 3 | import clojure.lang.*; 4 | import missionary.Cancelled; 5 | 6 | import java.util.Iterator; 7 | 8 | public interface Seed { 9 | 10 | final class Process extends AFn implements IDeref { 11 | static { 12 | Util.printDefault(Process.class); 13 | } 14 | 15 | IFn notifier; 16 | IFn terminator; 17 | volatile Iterator iterator; 18 | 19 | @Override 20 | public Object invoke() { 21 | cancel(this); 22 | return null; 23 | } 24 | 25 | @Override 26 | public Object deref() { 27 | return transfer(this); 28 | } 29 | } 30 | 31 | static void cancel(Process ps) { 32 | ps.iterator = null; 33 | } 34 | 35 | static Object transfer(Process ps) { 36 | Iterator i = ps.iterator; 37 | if (i == null) { 38 | ps.terminator.invoke(); 39 | clojure.lang.Util.sneakyThrow(new Cancelled("Seed cancelled.")); 40 | } 41 | Object x = i.next(); 42 | more(ps, i); 43 | return x; 44 | } 45 | 46 | // TODO handle exceptions thrown by iterator 47 | static void more(Process ps, Iterator i) { 48 | if (i.hasNext()) ps.notifier.invoke(); 49 | else { 50 | ps.iterator = null; 51 | ps.terminator.invoke(); 52 | } 53 | } 54 | 55 | static Process run(Object coll, IFn n, IFn t) { 56 | Process ps = new Process(); 57 | Iterator i = RT.iter(coll); 58 | ps.notifier = n; 59 | ps.terminator = t; 60 | ps.iterator = i; 61 | more(ps, i); 62 | return ps; 63 | } 64 | } -------------------------------------------------------------------------------- /java/missionary/impl/Semaphore.java: -------------------------------------------------------------------------------- 1 | package missionary.impl; 2 | 3 | import clojure.lang.*; 4 | import missionary.Cancelled; 5 | 6 | import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; 7 | 8 | import static missionary.impl.Util.NOP; 9 | 10 | public interface Semaphore { 11 | 12 | AtomicReferenceFieldUpdater STATE = 13 | AtomicReferenceFieldUpdater.newUpdater(Port.class, Object.class, "state"); 14 | 15 | final class Port extends AFn implements Event.Emitter { 16 | static { 17 | Util.printDefault(Port.class); 18 | } 19 | 20 | volatile Object state; 21 | 22 | Port(int n) { 23 | state = n; 24 | } 25 | 26 | @Override 27 | public Object invoke() { 28 | release(this); 29 | return null; 30 | } 31 | 32 | @Override 33 | public Object invoke(Object s, Object f) { 34 | return acquire(this, (IFn) s, (IFn) f); 35 | } 36 | 37 | @Override 38 | public void cancel(Event e) { 39 | cancelAcquire(this, e); 40 | } 41 | } 42 | 43 | static void release(Port port) { 44 | for(;;) { 45 | Object s = port.state; 46 | if (s instanceof IPersistentSet) { 47 | Event e = (Event) RT.iter(s).next(); 48 | IPersistentSet set = (IPersistentSet) s; 49 | if (STATE.compareAndSet(port, s, set.count() == 1 ? null : set.disjoin(e))) { 50 | e.success.invoke(null); 51 | break; 52 | } 53 | } else if (STATE.compareAndSet(port, s, s == null ? 1 : ((Integer) s) + 1)) break; 54 | } 55 | } 56 | 57 | static IFn acquire(Port port, IFn success, IFn failure) { 58 | for(;;) { 59 | Object s = port.state; 60 | if (s instanceof Integer) { 61 | int n = (Integer) s; 62 | if (STATE.compareAndSet(port, s, n == 1 ? null : n - 1)) { 63 | success.invoke(null); 64 | return NOP; 65 | } 66 | } else { 67 | Event e = new Event(port, success, failure); 68 | IPersistentSet set = (s == null) ? PersistentHashSet.EMPTY : (IPersistentSet) s; 69 | if (STATE.compareAndSet(port, s, set.cons(e))) return e; 70 | } 71 | } 72 | } 73 | 74 | static void cancelAcquire(Port port, Event e) { 75 | for(;;) { 76 | Object s = port.state; 77 | if (!(s instanceof IPersistentSet)) break; 78 | IPersistentSet set = (IPersistentSet) s; 79 | if (!(set.contains(e))) break; 80 | if (STATE.compareAndSet(port, s, set.count() == 1 ? null : set.disjoin(e))) { 81 | e.failure.invoke(new Cancelled("Semaphore acquire cancelled.")); 82 | break; 83 | } 84 | } 85 | } 86 | 87 | static Port make(int permits) { 88 | return new Port(permits); 89 | } 90 | } 91 | 92 | 93 | -------------------------------------------------------------------------------- /java/missionary/impl/Sequential.java: -------------------------------------------------------------------------------- 1 | package missionary.impl; 2 | 3 | import clojure.lang.*; 4 | import missionary.Cancelled; 5 | 6 | import static missionary.impl.Fiber.fiber; 7 | 8 | public interface Sequential { 9 | 10 | class Process extends AFn implements Fiber { 11 | 12 | static { 13 | Util.printDefault(Process.class); 14 | } 15 | 16 | IFn coroutine; 17 | IFn success; 18 | IFn failure; 19 | IFn resume; 20 | IFn rethrow; 21 | 22 | boolean busy; 23 | boolean failed; 24 | Object current; 25 | IFn token = Util.NOP; 26 | 27 | @Override 28 | public synchronized Object invoke() { 29 | kill(this); 30 | return null; 31 | } 32 | 33 | @Override 34 | public synchronized Object check() { 35 | return token == null ? clojure.lang.Util.sneakyThrow(new Cancelled("Process cancelled.")) : null; 36 | } 37 | 38 | @Override 39 | public Object park(IFn t) { 40 | return suspend(this, t); 41 | } 42 | 43 | @Override 44 | public Object swich(IFn f) { 45 | throw new UnsupportedOperationException(); 46 | } 47 | 48 | @Override 49 | public Object fork(Number b, IFn f) { 50 | throw new UnsupportedOperationException(); 51 | } 52 | 53 | @Override 54 | public Object unpark() { 55 | Object x = current; 56 | current = null; 57 | if (failed) { 58 | failed = false; 59 | clojure.lang.Util.sneakyThrow((Throwable) x); 60 | } 61 | return x; 62 | } 63 | } 64 | 65 | static void kill(Process ps) { 66 | IFn c = ps.token; 67 | if (c != null) { 68 | ps.token = null; 69 | c.invoke(); 70 | } 71 | } 72 | 73 | static Object suspend(Process ps, IFn task) { 74 | IFn c = (IFn) task.invoke(ps.resume, ps.rethrow); 75 | if (ps.token == null) c.invoke(); 76 | else ps.token = c; 77 | return ps; 78 | } 79 | 80 | static void step(Process ps) { 81 | Object x = null; 82 | IFn cb = null; 83 | synchronized (ps) { 84 | if (ps.busy = !ps.busy) { 85 | Fiber prev = fiber.get(); 86 | fiber.set(ps); 87 | try { 88 | for(;;) if (ps == (x = ps.coroutine.invoke())) if (ps.busy = !ps.busy) {} 89 | else break; else { 90 | cb = ps.success; 91 | break; 92 | } 93 | } catch (Throwable e) { 94 | x = e; 95 | cb = ps.failure; 96 | } 97 | fiber.set(prev); 98 | } 99 | } 100 | if (cb != null) cb.invoke(x); 101 | } 102 | 103 | static Process run(IFn c, IFn s, IFn f) { 104 | Process ps = new Process(); 105 | ps.coroutine = c; 106 | ps.success = s; 107 | ps.failure = f; 108 | ps.resume = new AFn() { 109 | @Override 110 | public Object invoke(Object x) { 111 | ps.current = x; 112 | step(ps); 113 | return null; 114 | } 115 | }; 116 | ps.rethrow = new AFn() { 117 | @Override 118 | public Object invoke(Object x) { 119 | ps.failed = true; 120 | ps.current = x; 121 | step(ps); 122 | return null; 123 | } 124 | }; 125 | step(ps); 126 | return ps; 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /java/missionary/impl/Sleep.java: -------------------------------------------------------------------------------- 1 | package missionary.impl; 2 | 3 | import clojure.lang.AFn; 4 | import clojure.lang.IFn; 5 | import missionary.Cancelled; 6 | 7 | import java.util.concurrent.TimeUnit; 8 | import java.util.concurrent.locks.Condition; 9 | import java.util.concurrent.locks.Lock; 10 | import java.util.concurrent.locks.ReentrantLock; 11 | 12 | public interface Sleep { 13 | Scheduler S = new Scheduler(); 14 | Cancelled C = new Cancelled("Sleep cancelled."); 15 | 16 | final class Scheduler extends Thread { 17 | final Lock L = new ReentrantLock(); 18 | final Condition C = L.newCondition(); 19 | 20 | Process queue = null; 21 | 22 | Scheduler() { 23 | super("missionary scheduler"); 24 | setDaemon(true); 25 | start(); 26 | } 27 | 28 | @Override 29 | public void run() { 30 | for(;;) try { 31 | IFn s = null; 32 | Object x = null; 33 | L.lock(); 34 | Process head = queue; 35 | if (head == null) C.await(); else { 36 | long d = head.time - System.currentTimeMillis(); 37 | if (0 < d) C.await(d, TimeUnit.MILLISECONDS); else { 38 | s = head.success; 39 | x = head.payload; 40 | head.payload = null; 41 | head.success = null; 42 | head.failure = null; 43 | queue = dequeue(head); 44 | } 45 | } 46 | L.unlock(); 47 | if (s != null) s.invoke(x); 48 | } catch (Throwable e) { 49 | e.printStackTrace(); 50 | } 51 | } 52 | } 53 | 54 | final class Process extends AFn { 55 | static { 56 | Util.printDefault(Process.class); 57 | } 58 | 59 | Object payload; 60 | IFn success; 61 | IFn failure; 62 | long time; 63 | Process sibling; 64 | Process child; 65 | 66 | @Override 67 | public Object invoke() { 68 | IFn f; 69 | S.L.lock(); 70 | f = failure; 71 | failure = null; 72 | success = null; 73 | payload = null; 74 | S.L.unlock(); 75 | return f == null ? null : f.invoke(C); 76 | } 77 | } 78 | 79 | static Process link(Process x, Process y) { 80 | if (x.time < y.time) { 81 | y.sibling = x.child; 82 | x.child = y; 83 | return x; 84 | } else { 85 | x.sibling = y.child; 86 | y.child = x; 87 | return y; 88 | } 89 | } 90 | 91 | static Process dequeue(Process ps) { 92 | Process heap = null; 93 | Process prev = null; 94 | Process head = ps.child; 95 | ps.child = ps; 96 | while (head != null) { 97 | Process next = head.sibling; 98 | head.sibling = null; 99 | if (prev == null) prev = head; 100 | else { 101 | head = link(prev, head); 102 | heap = heap == null ? head : link(heap, head); 103 | prev = null; 104 | } 105 | head = next; 106 | } 107 | return prev == null ? heap : heap == null ? prev : link(heap, prev); 108 | } 109 | 110 | static Process run(long d, Object x, IFn s, IFn f) { 111 | Process ps = new Process(); 112 | ps.payload = x; 113 | ps.success = s; 114 | ps.failure = f; 115 | ps.time = System.currentTimeMillis() + d; 116 | S.L.lock(); 117 | Process head = S.queue; 118 | S.queue = head == null ? ps : link(head, ps); 119 | S.C.signal(); 120 | S.L.unlock(); 121 | return ps; 122 | } 123 | } -------------------------------------------------------------------------------- /java/missionary/impl/Sub.java: -------------------------------------------------------------------------------- 1 | package missionary.impl; 2 | 3 | import clojure.lang.AFn; 4 | import clojure.lang.IDeref; 5 | import clojure.lang.IFn; 6 | import missionary.Cancelled; 7 | import org.reactivestreams.Publisher; 8 | import org.reactivestreams.Subscriber; 9 | import org.reactivestreams.Subscription; 10 | 11 | public interface Sub { 12 | 13 | final class Process extends AFn implements IDeref, Subscriber { 14 | 15 | static { 16 | Util.printDefault(Process.class); 17 | } 18 | 19 | IFn terminator; 20 | IFn notifier; 21 | Subscription sub; 22 | Object current; 23 | Object result; 24 | 25 | @Override 26 | public Object invoke() { 27 | cancel(this); 28 | return null; 29 | } 30 | 31 | @Override 32 | public Object deref() { 33 | return transfer(this); 34 | } 35 | 36 | @Override 37 | public void onSubscribe(Subscription s) { 38 | if (s == null) throw new NullPointerException(); 39 | subscribe(this, s); 40 | } 41 | 42 | @Override 43 | public void onNext(Object x) { 44 | if (x == null) throw new NullPointerException(); 45 | next(this, x); 46 | } 47 | 48 | @Override 49 | public void onError(Throwable e) { 50 | if (e == null) throw new NullPointerException(); 51 | error(this, e); 52 | } 53 | 54 | @Override 55 | public void onComplete() { 56 | complete(this); 57 | } 58 | } 59 | 60 | static void cancel(Process ps) { 61 | Subscription sub; 62 | Object current; 63 | Object result; 64 | synchronized (ps) { 65 | sub = ps.sub; 66 | result = ps.result; 67 | current = ps.current; 68 | if (result == null) ps.result = new Cancelled("Subscription cancelled."); 69 | } 70 | if (result == null) { 71 | if (sub != null) sub.cancel(); 72 | if (current == null) ps.notifier.invoke(); 73 | } 74 | } 75 | 76 | static Object transfer(Process ps) { 77 | Object result; 78 | Object current; 79 | synchronized (ps) { 80 | result = ps.result; 81 | current = ps.current; 82 | ps.current = null; 83 | } 84 | if (current == null) { 85 | ps.terminator.invoke(); 86 | return clojure.lang.Util.sneakyThrow((Throwable) result); 87 | } else { 88 | if (result == null) ps.sub.request(1); 89 | else (result == ps ? ps.terminator : ps.notifier).invoke(); 90 | return current; 91 | } 92 | } 93 | 94 | static void subscribe(Process ps, Subscription sub) { 95 | Object result; 96 | Subscription prev; 97 | synchronized (ps) { 98 | prev = ps.sub; 99 | result = ps.result; 100 | if (prev == null && result == null) ps.sub = sub; 101 | } 102 | if (prev == null && result == null) sub.request(1); 103 | else sub.cancel(); 104 | } 105 | 106 | static void next(Process ps, Object x) { 107 | Object result; 108 | synchronized (ps) { 109 | result = ps.result; 110 | if (result == null) ps.current = x; 111 | } 112 | if (result == null) ps.notifier.invoke(); 113 | } 114 | 115 | static void error(Process ps, Throwable err) { 116 | Object current; 117 | Object result; 118 | synchronized (ps) { 119 | current = ps.current; 120 | result = ps.result; 121 | if (result == null) ps.result = err; 122 | } 123 | if (result == null && current == null) ps.notifier.invoke(); 124 | } 125 | 126 | static void complete(Process ps) { 127 | Object current; 128 | Object result; 129 | synchronized (ps) { 130 | current = ps.current; 131 | result = ps.result; 132 | if (result == null) ps.result = ps; 133 | } 134 | if (result == null) (current == null ? ps.terminator : ps.notifier).invoke(); 135 | } 136 | 137 | static Process run(Publisher p, IFn n, IFn t) { 138 | Process ps = new Process(); 139 | ps.notifier = n; 140 | ps.terminator = t; 141 | p.subscribe(ps); 142 | return ps; 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /java/missionary/impl/Thunk.java: -------------------------------------------------------------------------------- 1 | package missionary.impl; 2 | 3 | import clojure.lang.AFn; 4 | import clojure.lang.IFn; 5 | 6 | import java.util.concurrent.Executor; 7 | import java.util.concurrent.Executors; 8 | import java.util.concurrent.atomic.AtomicInteger; 9 | 10 | public interface Thunk { 11 | 12 | final class Cpu extends Thread { 13 | static final AtomicInteger ID = new AtomicInteger(); 14 | static final Executor POOL = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors(), r -> { 15 | Thread t = new Thread(r, "missionary cpu-" + ID.getAndIncrement()); 16 | t.setDaemon(true); 17 | return t; 18 | }); 19 | } 20 | 21 | final class Blk extends Thread { 22 | static final AtomicInteger ID = new AtomicInteger(); 23 | static final Executor POOL = Executors.newCachedThreadPool(r -> { 24 | Thread t = new Thread(r, "missionary blk-" + ID.getAndIncrement()); 25 | t.setDaemon(true); 26 | return t; 27 | }); 28 | } 29 | 30 | Executor cpu = Cpu.POOL; 31 | Executor blk = Blk.POOL; 32 | 33 | final class Process extends AFn { 34 | static { 35 | Util.printDefault(Process.class); 36 | } 37 | 38 | final IFn thunk; 39 | final IFn success; 40 | final IFn failure; 41 | 42 | Object thread; 43 | 44 | public Process(Executor e, IFn t, IFn s, IFn f) { 45 | thunk = t; 46 | success = s; 47 | failure = f; 48 | e.execute(this); 49 | } 50 | 51 | @Override 52 | public void run() { 53 | synchronized (this) { 54 | Thread t = Thread.currentThread(); 55 | if (thread == this) t.interrupt(); 56 | else thread = t; 57 | } 58 | Object x; 59 | IFn cont; 60 | try { 61 | x = thunk.invoke(); 62 | cont = success; 63 | } catch (Throwable e) { 64 | x = e; 65 | cont = failure; 66 | } 67 | synchronized (this) { 68 | if (thread == this) Thread.interrupted(); 69 | else thread = this; 70 | } 71 | cont.invoke(x); 72 | } 73 | 74 | @Override 75 | public Object invoke() { 76 | synchronized (this) { 77 | Object t = thread; 78 | if (t != this) { 79 | thread = this; 80 | if (t != null) ((Thread) t).interrupt(); 81 | } 82 | } 83 | return null; 84 | } 85 | } 86 | 87 | static Process run(Executor e, IFn t, IFn s, IFn f) { 88 | return new Process(e, t, s, f); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /java/missionary/impl/Util.java: -------------------------------------------------------------------------------- 1 | package missionary.impl; 2 | 3 | import clojure.lang.*; 4 | 5 | import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; 6 | 7 | final class Util { 8 | static IFn NOP = new AFn() { 9 | @Override 10 | public Object invoke() { 11 | return null; 12 | } 13 | }; 14 | 15 | static void swap(T that, AtomicReferenceFieldUpdater field, IFn cancel) { 16 | for(;;) { 17 | IFn current = field.get(that); 18 | if (current == null) { 19 | cancel.invoke(); 20 | break; 21 | } 22 | if (field.compareAndSet(that, current, cancel)) break; 23 | } 24 | } 25 | 26 | static Object apply(IFn f, Object[] args) { 27 | switch (args.length) { 28 | case 0: return f.invoke(); 29 | case 1: return f.invoke(args[0]); 30 | case 2: return f.invoke(args[0], args[1]); 31 | case 3: return f.invoke(args[0], args[1], args[2]); 32 | case 4: return f.invoke(args[0], args[1], args[2], args[3]); 33 | case 5: return f.invoke(args[0], args[1], args[2], args[3], args[4]); 34 | case 6: return f.invoke(args[0], args[1], args[2], args[3], args[4], args[5]); 35 | case 7: return f.invoke(args[0], args[1], args[2], args[3], args[4], args[5], args[6]); 36 | case 8: return f.invoke(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7]); 37 | case 9: return f.invoke(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8]); 38 | case 10: return f.invoke(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9]); 39 | case 11: return f.invoke(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9], args[10]); 40 | case 12: return f.invoke(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9], args[10], args[11]); 41 | case 13: return f.invoke(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9], args[10], args[11], args[12]); 42 | case 14: return f.invoke(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9], args[10], args[11], args[12], args[13]); 43 | case 15: return f.invoke(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9], args[10], args[11], args[12], args[13], args[14]); 44 | case 16: return f.invoke(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9], args[10], args[11], args[12], args[13], args[14], args[15]); 45 | case 17: return f.invoke(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9], args[10], args[11], args[12], args[13], args[14], args[15], args[16]); 46 | case 18: return f.invoke(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9], args[10], args[11], args[12], args[13], args[14], args[15], args[16], args[17]); 47 | case 19: return f.invoke(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9], args[10], args[11], args[12], args[13], args[14], args[15], args[16], args[17], args[18]); 48 | case 20: return f.invoke(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9], args[10], args[11], args[12], args[13], args[14], args[15], args[16], args[17], args[18], args[19]); 49 | default: 50 | Object[] rest = new Object[args.length - 20]; 51 | System.arraycopy(args, 20, rest, 0, rest.length); 52 | return f.invoke(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9], args[10], args[11], args[12], args[13], args[14], args[15], args[16], args[17], args[18], args[19], rest); 53 | } 54 | } 55 | 56 | static void printDefault(Class c) { 57 | MultiFn pm = (MultiFn) ((Var) clojure.java.api.Clojure.var("clojure.core", "print-method")).deref(); 58 | pm.addMethod(c, pm.getMethod(Object.class)); 59 | } 60 | 61 | static void discard(Object it) { 62 | try { 63 | ((IDeref) it).deref(); 64 | } catch (Throwable e) { 65 | } 66 | } 67 | 68 | } 69 | -------------------------------------------------------------------------------- /java/missionary/impl/Watch.java: -------------------------------------------------------------------------------- 1 | package missionary.impl; 2 | 3 | import clojure.lang.AFn; 4 | import clojure.lang.IFn; 5 | import clojure.lang.IRef; 6 | import clojure.lang.IDeref; 7 | import missionary.Cancelled; 8 | 9 | public interface Watch { 10 | 11 | class Process extends AFn implements IDeref { 12 | 13 | static { 14 | Util.printDefault(Process.class); 15 | } 16 | 17 | IFn notifier; 18 | IFn terminator; 19 | IRef reference; 20 | Object value; 21 | 22 | @Override 23 | public Object invoke() { 24 | kill(this); 25 | return null; 26 | } 27 | 28 | @Override 29 | public Object deref() { 30 | return transfer(this); 31 | } 32 | } 33 | 34 | IFn watch = new AFn() { 35 | @Override 36 | public Object invoke(Object key, Object ref, Object prev, Object curr) { 37 | Process ps = (Process) key; 38 | IFn cb; 39 | synchronized (ps) { 40 | cb = ps.notifier; 41 | if (cb != null) { 42 | if (ps.value != ps) cb = null; 43 | ps.value = curr; 44 | } 45 | } 46 | return cb == null ? null : cb.invoke(); 47 | } 48 | }; 49 | 50 | static void kill(Process ps) { 51 | IFn cb; 52 | synchronized (ps) { 53 | cb = ps.notifier; 54 | if (cb != null) { 55 | if (ps.value != ps) cb = null; 56 | ps.value = ps.notifier = null; 57 | } 58 | } 59 | if (cb != null) cb.invoke(); 60 | } 61 | 62 | static Object transfer(Process ps) { 63 | if (ps.notifier == null) { 64 | ps.terminator.invoke(); 65 | ps.reference.removeWatch(ps); 66 | return clojure.lang.Util.sneakyThrow(new Cancelled("Watch cancelled.")); 67 | } else synchronized (ps) { 68 | Object x = ps.value; 69 | ps.value = ps; 70 | return x; 71 | } 72 | } 73 | 74 | static Object run(IRef r, IFn n, IFn t) { 75 | Process ps = new Process(); 76 | ps.notifier = n; 77 | ps.terminator = t; 78 | ps.reference = r; 79 | ps.value = r.deref(); 80 | r.addWatch(ps, watch); 81 | n.invoke(); 82 | return ps; 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /java/missionary/impl/Zip.java: -------------------------------------------------------------------------------- 1 | package missionary.impl; 2 | 3 | import clojure.lang.AFn; 4 | import clojure.lang.IDeref; 5 | import clojure.lang.IFn; 6 | import clojure.lang.RT; 7 | 8 | import java.util.Iterator; 9 | import java.util.concurrent.locks.Lock; 10 | import java.util.concurrent.locks.ReentrantLock; 11 | 12 | public interface Zip { 13 | 14 | final class Process extends AFn implements IDeref { 15 | 16 | static { 17 | Util.printDefault(Process.class); 18 | } 19 | 20 | Lock lock; 21 | IFn combine; 22 | IFn step; 23 | IFn done; 24 | Object[] inputs; 25 | int pending; 26 | 27 | @Override 28 | public Object invoke() { 29 | cancel(this); 30 | return null; 31 | } 32 | 33 | @Override 34 | public Object deref() { 35 | return transfer(this); 36 | } 37 | } 38 | 39 | static void ready(Process ps) { 40 | IFn s = ps.step; 41 | if (s == null) { 42 | int p, c = 0; 43 | ps.lock.lock(); 44 | for (Object input : ps.inputs) if (input != ps) try { 45 | c++; 46 | ((IDeref) input).deref(); 47 | } catch (Throwable e) {} 48 | p = ps.pending += c; 49 | ps.lock.unlock(); 50 | if (0 == c) ps.done.invoke(); 51 | else if (0 == p) ready(ps); 52 | } else s.invoke(); 53 | } 54 | 55 | static void cancel(Process ps) { 56 | for (Object input: ps.inputs) if (input != ps) ((IFn) input).invoke(); 57 | } 58 | 59 | static Object transfer(Process ps) { 60 | int c = 0; 61 | Object[] inputs = ps.inputs; 62 | int arity = inputs.length; 63 | Object[] buffer = new Object[arity]; 64 | ps.lock.lock(); 65 | try { 66 | for (int i = 0; i < arity; i++) { 67 | c++; 68 | buffer[i] = ((IDeref) inputs[i]).deref(); 69 | } 70 | return Util.apply(ps.combine, buffer); 71 | } catch (Throwable e) { 72 | ps.step = null; 73 | throw e; 74 | } finally { 75 | int p = ps.pending += c; 76 | ps.lock.unlock(); 77 | if (ps.step == null) cancel(ps); 78 | if (0 == p) ready(ps); 79 | } 80 | } 81 | 82 | static Process run(IFn f, Object fs, IFn s, IFn d) { 83 | int arity = RT.count(fs); 84 | Object[] inputs = new Object[arity]; 85 | Lock lock = new ReentrantLock(); 86 | Process ps = new Process(); 87 | ps.lock = lock; 88 | ps.combine = f; 89 | ps.step = s; 90 | ps.done = d; 91 | ps.inputs = inputs; 92 | Iterator it = RT.iter(fs); 93 | int i = 0; 94 | lock.lock(); 95 | do { 96 | int index = i++; 97 | Object input = ((IFn) it.next()).invoke( 98 | new AFn() { 99 | @Override 100 | public Object invoke() { 101 | ps.lock.lock(); 102 | int p = --ps.pending; 103 | ps.lock.unlock(); 104 | if (0 == p) ready(ps); 105 | return null; 106 | } 107 | }, 108 | new AFn() { 109 | @Override 110 | public Object invoke() { 111 | ps.lock.lock(); 112 | ps.inputs[index] = ps; 113 | ps.step = null; 114 | int p = --ps.pending; 115 | ps.lock.unlock(); 116 | if (0 <= p) cancel(ps); 117 | if (0 == p) ready(ps); 118 | return null; 119 | } 120 | } 121 | ); 122 | if (inputs[index] == null) 123 | inputs[index] = input; 124 | } while (it.hasNext()); 125 | int p = ps.pending += arity; 126 | lock.unlock(); 127 | if (ps.step == null) cancel(ps); 128 | if (0 == p) ready(ps); 129 | return ps; 130 | } 131 | 132 | } 133 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | missionary 5 | missionary 6 | b.45 7 | missionary 8 | A functional effect and streaming system for clojure and clojurescript. 9 | https://github.com/leonoel/missionary 10 | 11 | 12 | Eclipse Public License 13 | https://www.eclipse.org/org/documents/epl-2.0/EPL-2.0.html 14 | 15 | 16 | 17 | scm:git:git://github.com/leonoel/missionary.git 18 | scm:git:ssh://git@github.com/leonoel/missionary.git 19 | a976ebf7c77cec8b06e7379fbb122aff1f0cd8b7 20 | https://github.com/leonoel/missionary 21 | 22 | 23 | 24 | clojars 25 | Clojars repository 26 | https://clojars.org/repo 27 | 28 | 29 | 30 | java 31 | 32 | 33 | src 34 | 35 | 36 | target 37 | target/classes 38 | 39 | 40 | org.apache.maven.plugins 41 | maven-compiler-plugin 42 | 3.12.1 43 | 44 | 8 45 | 8 46 | UTF-8 47 | 48 | 49 | 50 | org.apache.maven.plugins 51 | maven-source-plugin 52 | 3.3.0 53 | 54 | 55 | attach-sources 56 | 57 | jar 58 | 59 | 60 | 61 | 62 | 63 | org.apache.maven.plugins 64 | maven-resources-plugin 65 | 2.6 66 | 67 | UTF-8 68 | 69 | 70 | 71 | 72 | 73 | 74 | clojars 75 | https://repo.clojars.org/ 76 | 77 | 78 | 79 | 80 | org.clojure 81 | clojure 82 | 1.11.1 83 | 84 | 85 | cloroutine 86 | cloroutine 87 | 13 88 | 89 | 90 | org.reactivestreams 91 | reactive-streams 92 | 1.0.4 93 | 94 | 95 | 96 | -------------------------------------------------------------------------------- /src/missionary/Cancelled.js: -------------------------------------------------------------------------------- 1 | goog.provide('missionary.Cancelled'); 2 | 3 | /** 4 | * @param {string} message 5 | * @constructor 6 | */ 7 | missionary.Cancelled = function(message) { 8 | 'use strict'; 9 | 10 | /** 11 | * The error message. 12 | * @const {string} 13 | */ 14 | this.message = message; 15 | 16 | }; 17 | -------------------------------------------------------------------------------- /src/missionary/impl/Buffer.cljs: -------------------------------------------------------------------------------- 1 | (ns missionary.impl.Buffer) 2 | 3 | (declare kill transfer) 4 | (deftype Process 5 | [notifier terminator 6 | iterator buffer 7 | ^number failed 8 | ^number size 9 | ^number push 10 | ^number pull 11 | ^boolean busy 12 | ^boolean done] 13 | IFn 14 | (-invoke [ps] (kill ps)) 15 | IDeref 16 | (-deref [ps] (transfer ps))) 17 | 18 | (defn more [^Process ps] 19 | (let [buffer (.-buffer ps)] 20 | (loop [cb nil] 21 | (if (set! (.-busy ps) (not (.-busy ps))) 22 | (let [i (.-push ps) 23 | s (.-size ps)] 24 | (set! (.-push ps) (js-mod (inc i) (alength buffer))) 25 | (let [cb (if (zero? s) (if (.-done ps) (.-terminator ps) (.-notifier ps)) cb)] 26 | (if (.-done ps) 27 | (aset buffer i ps) 28 | (try (aset buffer i @(.-iterator ps)) 29 | (catch :default e 30 | (set! (.-failed ps) i) 31 | (aset buffer i e)))) 32 | (if (== (set! (.-size ps) (inc s)) (alength buffer)) 33 | cb (recur cb)))) cb)))) 34 | 35 | (defn transfer [^Process ps] 36 | (let [buffer (.-buffer ps) 37 | i (.-pull ps) 38 | s (.-size ps) 39 | n (js-mod (inc i) (alength buffer)) 40 | f (== i (.-failed ps)) 41 | x (aget buffer i)] 42 | (aset buffer i nil) 43 | (set! (.-pull ps) n) 44 | (set! (.-size ps) (dec s)) 45 | (let [cb (when (== s (alength buffer)) (more ps))] 46 | (when-some [cb (if (== s 1) 47 | cb (if (identical? (aget buffer n) ps) 48 | (.-terminator ps) (.-notifier ps)))] 49 | (cb))) (if f (throw x) x))) 50 | 51 | (defn kill [^Process ps] 52 | ((.-iterator ps))) 53 | 54 | (deftype Flow [capacity input] 55 | IFn 56 | (-invoke [_ n t] 57 | (let [ps (->Process n t nil (object-array capacity) -1 0 0 0 true false)] 58 | (set! (.-iterator ps) 59 | (input #(when-some [cb (more ps)] (cb)) 60 | #(do (set! (.-done ps) true) 61 | (when-some [cb (more ps)] (cb))))) 62 | (when-some [cb (more ps)] (cb)) ps))) 63 | 64 | (def flow ->Flow) -------------------------------------------------------------------------------- /src/missionary/impl/Dataflow.cljs: -------------------------------------------------------------------------------- 1 | (ns missionary.impl.Dataflow 2 | (:import missionary.Cancelled)) 3 | 4 | (defn nop []) 5 | (defn send-rf [x !] (! x) x) 6 | 7 | (deftype Port [^:mutable bound 8 | ^:mutable value 9 | ^:mutable watch] 10 | IFn 11 | (-invoke [_ t] 12 | (when-not bound 13 | (set! bound true) 14 | (set! value t) 15 | (reduce send-rf t (persistent! watch)) 16 | (set! watch nil)) value) 17 | (-invoke [_ s! f!] 18 | (if bound 19 | (do (s! value) nop) 20 | (let [! #(s! %)] 21 | (set! watch (conj! watch !)) 22 | #(when-not bound 23 | (when (contains? watch !) 24 | (set! watch (disj! watch !)) 25 | (f! (Cancelled. "Dataflow variable dereference cancelled.")))))))) 26 | 27 | (defn make [] (->Port false nil (transient #{}))) 28 | -------------------------------------------------------------------------------- /src/missionary/impl/Eduction.cljs: -------------------------------------------------------------------------------- 1 | (ns missionary.impl.Eduction) 2 | 3 | (declare cancel transfer) 4 | (deftype Process 5 | [reducer iterator 6 | notifier terminator 7 | buffer 8 | ^number offset 9 | ^number length 10 | ^number error 11 | ^boolean busy 12 | ^boolean done] 13 | IFn 14 | (-invoke [t] (cancel t)) 15 | IDeref 16 | (-deref [t] (transfer t))) 17 | 18 | (defn feed 19 | ([^Process t] t) 20 | ([^Process t x] 21 | (if (== (.-length t) (alength (.-buffer t))) 22 | (.push (.-buffer t) x) 23 | (aset (.-buffer t) (.-length t) x)) 24 | (set! (.-length t) (inc (.-length t))) t)) 25 | 26 | (defn pull [^Process t] 27 | (loop [] 28 | (if (.-done t) 29 | (if-some [rf (.-reducer t)] 30 | (do (set! (.-offset t) 0) 31 | (set! (.-length t) 0) 32 | (try (rf t) 33 | (catch :default e 34 | (set! (.-error t) (.-length t)) 35 | (feed t e))) 36 | (set! (.-reducer t) nil) 37 | (if (zero? (.-length t)) 38 | (recur) 39 | (do ((.-notifier t)) 40 | (when (set! (.-busy t) (not (.-busy t))) (recur))))) 41 | ((.-terminator t))) 42 | (if-some [rf (.-reducer t)] 43 | (do (set! (.-offset t) 0) 44 | (set! (.-length t) 0) 45 | (try (when (reduced? (rf t @(.-iterator t))) 46 | (rf t) 47 | (set! (.-reducer t) nil) 48 | (cancel t)) 49 | (catch :default e 50 | (set! (.-error t) (.-length t)) 51 | (feed t e) 52 | (set! (.-reducer t) nil) 53 | (cancel t))) 54 | (if (pos? (.-length t)) 55 | ((.-notifier t)) 56 | (when (set! (.-busy t) (not (.-busy t))) (recur)))) 57 | (do (try @(.-iterator t) (catch :default _)) 58 | (when (set! (.-busy t) (not (.-busy t))) (recur))))))) 59 | 60 | (defn cancel [^Process t] 61 | ((.-iterator t))) 62 | 63 | (defn transfer [^Process t] 64 | (let [o (.-offset t) 65 | x (aget (.-buffer t) o)] 66 | (aset (.-buffer t) o nil) 67 | (set! (.-offset t) (inc o)) 68 | (if (== (.-offset t) (.-length t)) 69 | (when (set! (.-busy t) (not (.-busy t))) 70 | (pull t)) ((.-notifier t))) 71 | (if (== o (.-error t)) (throw x) x))) 72 | 73 | (defn run [xf flow n t] 74 | (let [t (->Process (xf feed) nil n t (object-array 1) 0 0 -1 true false) 75 | n #(when (set! (.-busy t) (not (.-busy t))) (pull t))] 76 | (set! (.-iterator t) (flow n #(do (set! (.-done t) true) (n)))) 77 | (n) t)) -------------------------------------------------------------------------------- /src/missionary/impl/Fiber.cljs: -------------------------------------------------------------------------------- 1 | (ns ^:no-doc missionary.impl.Fiber) 2 | 3 | (defprotocol Fiber 4 | (park [_ t]) 5 | (swich [_ f]) 6 | (fork [_ p f]) 7 | (check [_]) 8 | (unpark [_])) 9 | 10 | (deftype Default [] 11 | Fiber 12 | (park [_ _] (throw (js/Error. "Unsupported operation."))) 13 | (swich [_ _] (throw (js/Error. "Unsupported operation."))) 14 | (fork [_ _ _] (throw (js/Error. "Unsupported operation."))) 15 | (check [_] (throw (js/Error. "Unsupported operation."))) 16 | (unpark [_] (throw (js/Error. "Unsupported operation.")))) 17 | 18 | (def fiber (->Default)) 19 | 20 | (defn current [] fiber) -------------------------------------------------------------------------------- /src/missionary/impl/GroupBy.cljs: -------------------------------------------------------------------------------- 1 | (ns ^:no-doc missionary.impl.GroupBy 2 | (:import missionary.Cancelled)) 3 | 4 | (declare kill group sample cancel consume) 5 | 6 | (deftype Process [keyfn notifier terminator 7 | key value input table 8 | ^number load 9 | ^boolean live 10 | ^boolean busy 11 | ^boolean done] 12 | IFn 13 | (-invoke [p] (kill p) nil) 14 | (-invoke [p n t] (group p n t)) 15 | IDeref 16 | (-deref [p] (sample p))) 17 | 18 | (deftype Group [process key notifier terminator] 19 | IFn 20 | (-invoke [g] (cancel g) nil) 21 | IDeref 22 | (-deref [g] (consume g))) 23 | 24 | (defn kill [^Process p] 25 | (when (.-live p) 26 | (set! (.-live p) false) 27 | ((.-input p)))) 28 | 29 | (defn step [^number i ^number m] 30 | (bit-and (inc i) m)) 31 | 32 | (defn group [^Process p n t] 33 | (let [k (.-key p) 34 | g (->Group p k n t) 35 | table (.-table p)] 36 | (when-not (identical? k p) 37 | (set! (.-key p) p) 38 | (let [s (alength table) 39 | m (dec s)] 40 | (loop [i (bit-and (hash k) m)] 41 | (case (aget table i) 42 | nil (aset table i g) 43 | (recur (step i m)))) 44 | (let [ss (bit-shift-left s 1)] 45 | (when (<= ss (* 3 (set! (.-load p) (inc (.-load p))))) 46 | (let [mm (dec ss) 47 | larger (object-array ss)] 48 | (set! (.-table p) larger) 49 | (dotimes [i s] 50 | (when-some [h (aget table i)] 51 | (loop [j (bit-and (hash (.-key h)) mm)] 52 | (case (aget larger j) 53 | nil (aset larger j h) 54 | (recur (step j mm))))))))))) 55 | (n) g)) 56 | 57 | (defn cancel [^Group g] 58 | (let [^Process p (.-process g) 59 | k (.-key g)] 60 | (when (.-live p) 61 | (when-not (identical? k p) 62 | (set! (.-key g) p) 63 | (let [table (.-table p) 64 | m (dec (alength table)) 65 | i (loop [i (bit-and (hash k) m)] 66 | (if (identical? g (aget table i)) 67 | i (recur (step i m))))] 68 | (aset table i nil) 69 | (set! (.-load p) (dec (.-load p))) 70 | (loop [i (step i m)] 71 | (when-some [h (aget table i)] 72 | (let [j (bit-and (hash (.-key h)) m)] 73 | (when-not (== i j) 74 | (aset table i nil) 75 | (loop [j j] 76 | (if (nil? (aget table j)) 77 | (aset table j h) 78 | (recur (step j m)))))) 79 | (recur (step i m)))) 80 | ((if (= k (.-key p)) 81 | (.-notifier p) 82 | (.-notifier g)))))))) 83 | 84 | (defn transfer [^Process p] 85 | (loop [] 86 | (when (set! (.-busy p) (not (.-busy p))) 87 | (if (.-done p) 88 | (do (set! (.-live p) false) 89 | (when-some [table (.-table p)] 90 | (set! (.-table p) nil) 91 | (dotimes [i (alength table)] 92 | (when-some [g (aget table i)] 93 | ((.-terminator g))))) 94 | ((.-terminator p))) 95 | (if (identical? p (.-value p)) 96 | (let [table (.-table p)] 97 | (try 98 | (let [k (set! (.-key p) ((.-keyfn p) (set! (.-value p) @(.-input p)))) 99 | m (dec (alength table))] 100 | (loop [i (bit-and (hash k) m)] 101 | (if-some [h (aget table i)] 102 | (if (= k (.-key h)) 103 | ((.-notifier h)) 104 | (recur (step i m))) 105 | ((.-notifier p))))) 106 | (catch :default e 107 | (set! (.-value p) e) 108 | (set! (.-table p) nil) 109 | (kill p) 110 | (dotimes [i (alength table)] 111 | (when-some [g (aget table i)] 112 | ((.-terminator g)))) 113 | ((.-notifier p))))) 114 | (do (try @(.-input p) (catch :default _)) 115 | (recur))))))) 116 | 117 | (defn sample [^Process p] 118 | (let [k (.-key p)] 119 | (if (identical? k p) 120 | (do (transfer p) (throw (.-value p))) 121 | (->MapEntry k p nil)))) 122 | 123 | (defn consume [^Group g] 124 | (let [^Process p (.-process g)] 125 | (if (identical? p (.-key g)) 126 | (do ((.-terminator g)) 127 | (throw (Cancelled. "Group consumer cancelled."))) 128 | (let [x (.-value p)] 129 | (set! (.-value p) p) 130 | (set! (.-key p) p) 131 | (transfer p) x)))) 132 | 133 | (defn run [k f n t] 134 | (let [p (->Process k n t nil nil nil (object-array 8) 0 true true false)] 135 | (set! (.-key p) p) 136 | (set! (.-value p) p) 137 | (set! (.-input p) 138 | (f #(transfer p) 139 | #(do (set! (.-done p) true) 140 | (transfer p)))) 141 | (transfer p) p)) 142 | -------------------------------------------------------------------------------- /src/missionary/impl/Heap.cljs: -------------------------------------------------------------------------------- 1 | (ns missionary.impl.Heap) 2 | 3 | (defn create [^number cap] 4 | (doto (js/Array. (inc cap)) 5 | (aset 0 0))) 6 | 7 | (defn size [heap] 8 | (aget heap 0)) 9 | 10 | (defn enqueue [heap i] 11 | (let [j (inc (aget heap 0))] 12 | (aset heap 0 j) 13 | (aset heap j i) 14 | (loop [j j] 15 | (when-not (== 1 j) 16 | (let [p (bit-shift-right j 1) 17 | x (aget heap j) 18 | y (aget heap p)] 19 | (when-not (< y x) 20 | (aset heap p x) 21 | (aset heap j y) 22 | (recur p))))))) 23 | 24 | (defn dequeue [heap] 25 | (let [s (aget heap 0) 26 | i (aget heap 1)] 27 | (aset heap 0 (dec s)) 28 | (aset heap 1 (aget heap s)) 29 | (loop [j 1] 30 | (let [l (bit-shift-left j 1)] 31 | (when (< l s) 32 | (let [x (aget heap j) 33 | y (aget heap l) 34 | r (inc l)] 35 | (if (< r s) 36 | (let [z (aget heap r)] 37 | (if (< y z) 38 | (when (< z x) 39 | (aset heap r x) 40 | (aset heap j z) 41 | (recur r)) 42 | (when (< y x) 43 | (aset heap l x) 44 | (aset heap j y) 45 | (recur l)))) 46 | (when (< y x) 47 | (aset heap l x) 48 | (aset heap j y) 49 | (recur l))))))) i)) -------------------------------------------------------------------------------- /src/missionary/impl/Latest.cljs: -------------------------------------------------------------------------------- 1 | (ns ^:no-doc missionary.impl.Latest 2 | (:require [missionary.impl.Heap :as h])) 3 | 4 | (declare kill transfer) 5 | 6 | (deftype Process [combinator notifier terminator value args inputs dirty ^number alive] 7 | IFn 8 | (-invoke [ps] (kill ps)) 9 | IDeref 10 | (-deref [ps] (transfer ps))) 11 | 12 | (defn kill [^Process ps] 13 | (let [inputs (.-inputs ps)] 14 | (dotimes [i (alength inputs)] 15 | ((aget inputs i))))) 16 | 17 | (defn transfer [^Process ps] 18 | (let [c (.-combinator ps) 19 | args (.-args ps) 20 | inputs (.-inputs ps) 21 | dirty (.-dirty ps) 22 | x (.-value ps) 23 | x (try (set! (.-value ps) ps) 24 | (when (nil? args) (throw (js/Error. "Undefined continuous flow."))) 25 | (loop [x x] 26 | (let [i (h/dequeue dirty) 27 | p (aget args i)] 28 | (aset args i @(aget inputs i)) 29 | (let [x (if (identical? x ps) 30 | x (if (= p (aget args i)) 31 | x ps))] 32 | (if (zero? (h/size dirty)) 33 | (if (identical? x ps) 34 | (let [x (.apply c nil args)] 35 | (if (zero? (h/size dirty)) 36 | x (recur x))) x) 37 | (recur x))))) 38 | (catch :default e 39 | (kill ps) 40 | (loop [] 41 | (when (pos? (h/size dirty)) 42 | (try @(aget inputs (h/dequeue dirty)) 43 | (catch :default _)) (recur))) 44 | (set! (.-notifier ps) nil) e))] 45 | (set! (.-value ps) x) 46 | (when (zero? (.-alive ps)) 47 | ((.-terminator ps))) 48 | (if (nil? (.-notifier ps)) 49 | (throw x) x))) 50 | 51 | (defn run [c fs n t] 52 | (let [it (iter fs) 53 | arity (count fs) 54 | args (object-array arity) 55 | inputs (object-array arity) 56 | dirty (h/create arity) 57 | ps (->Process c n t nil nil inputs dirty arity) 58 | done #(when (zero? (set! (.-alive ps) (dec (.-alive ps)))) 59 | (when-not (identical? (.-value ps) ps) 60 | ((.-terminator ps))))] 61 | (set! (.-value ps) ps) 62 | (dotimes [index arity] 63 | (aset inputs index 64 | ((.next it) 65 | #(do (h/enqueue dirty index) 66 | (when (== 1 (h/size dirty)) 67 | (when-not (identical? (.-value ps) ps) 68 | (if-some [n (.-notifier ps)] 69 | (n) (loop [] 70 | (try @(aget inputs (h/dequeue dirty)) 71 | (catch :default _)) 72 | (when (pos? (h/size dirty)) 73 | (recur))))))) done))) 74 | (when (== (h/size dirty) arity) 75 | (set! (.-args ps) args)) (n) ps)) -------------------------------------------------------------------------------- /src/missionary/impl/Mailbox.cljs: -------------------------------------------------------------------------------- 1 | (ns missionary.impl.Mailbox 2 | (:import missionary.Cancelled)) 3 | 4 | (defn nop []) 5 | 6 | (deftype Port [^:mutable enqueue 7 | ^:mutable dequeue 8 | ^:mutable readers] 9 | IFn 10 | (-invoke [_ t] 11 | (if-some [[!] (seq readers)] 12 | (do (set! readers (disj readers !)) (! t)) 13 | (do (.push enqueue t) nil))) 14 | (-invoke [_ s! f!] 15 | (if (zero? (alength dequeue)) 16 | (if (zero? (alength enqueue)) 17 | (let [! #(s! %)] 18 | (set! readers (conj readers !)) 19 | #(when (contains? readers !) 20 | (set! readers (disj readers !)) 21 | (f! (Cancelled. "Mailbox fetch cancelled.")))) 22 | (let [tmp enqueue] 23 | (set! enqueue dequeue) 24 | (set! dequeue (.reverse tmp)) 25 | (s! (.pop tmp)) nop)) 26 | (do (s! (.pop dequeue)) nop)))) 27 | 28 | (defn make [] (->Port (array) (array) #{})) 29 | -------------------------------------------------------------------------------- /src/missionary/impl/Never.cljs: -------------------------------------------------------------------------------- 1 | (ns missionary.impl.Never 2 | (:import missionary.Cancelled)) 3 | 4 | (declare cancel) 5 | (deftype Process [f ^boolean alive] 6 | IFn (-invoke [ps] (cancel ps))) 7 | 8 | (defn cancel [^Process ps] 9 | (when (.-alive ps) 10 | (set! (.-alive ps) false) 11 | ((.-f ps) (Cancelled. "Never cancelled.")))) 12 | 13 | (defn run [f] (->Process f true)) 14 | -------------------------------------------------------------------------------- /src/missionary/impl/Observe.cljs: -------------------------------------------------------------------------------- 1 | (ns ^:no-doc missionary.impl.Observe 2 | (:import missionary.Cancelled)) 3 | 4 | (declare kill transfer) 5 | 6 | (deftype Process [notifier terminator unsub value] 7 | IFn 8 | (-invoke [this] (kill this) nil) 9 | IDeref 10 | (-deref [this] (transfer this))) 11 | 12 | (defn kill [^Process ps] 13 | (when-some [cb (.-notifier ps)] 14 | (set! (.-notifier ps) nil) 15 | (try ((.-unsub ps)) 16 | (set! (.-unsub ps) (Cancelled. "Observe cancelled.")) 17 | (catch :default e 18 | (set! (.-unsub ps) e))) 19 | (let [x (.-value ps)] 20 | (set! (.-value ps) nil) 21 | (when (identical? x ps) (cb))))) 22 | 23 | (defn transfer [^Process ps] 24 | (if (nil? (.-notifier ps)) 25 | (do ((.-terminator ps)) 26 | (throw (.-unsub ps))) 27 | (let [x (.-value ps)] 28 | (set! (.-value ps) ps) x))) 29 | 30 | (defn run [s n t] 31 | (let [ps (->Process n t nil nil)] 32 | (set! (.-value ps) ps) 33 | (try (set! (.-unsub ps) 34 | (s (fn [x] 35 | (when-some [cb (.-notifier ps)] 36 | (if (identical? ps (.-value ps)) 37 | (do (set! (.-value ps) x) (cb)) 38 | (throw (js/Error. "Can't process event - consumer is not ready."))))))) 39 | (catch :default e 40 | (set! (.-unsub ps) e) 41 | (set! (.-notifier ps) nil) 42 | (if (identical? ps (.-value ps)) 43 | (n) (set! (.-value ps) ps)))) ps)) -------------------------------------------------------------------------------- /src/missionary/impl/RaceJoin.cljs: -------------------------------------------------------------------------------- 1 | (ns missionary.impl.RaceJoin) 2 | 3 | (declare cancel) 4 | (deftype Process 5 | [combinator 6 | joincb racecb 7 | children result 8 | ^number join 9 | ^number race] 10 | IFn 11 | (-invoke [j] (cancel j))) 12 | 13 | (defn cancel [^Process j] 14 | (dotimes [i (alength (.-children j))] 15 | ((aget (.-children j) i)))) 16 | 17 | (defn terminated [^Process j] 18 | (let [n (inc (.-join j))] 19 | (set! (.-join j) n) 20 | (when (== n (alength (.-result j))) 21 | (let [w (.-race j)] 22 | (if (neg? w) 23 | (try ((.-joincb j) (.apply (.-combinator j) nil (.-result j))) 24 | (catch :default e ((.-racecb j) e))) 25 | ((.-racecb j) (aget (.-result j) w))))))) 26 | 27 | (defn run [r c ts s f] 28 | (let [n (count ts) 29 | i (iter ts) 30 | j (->Process c (if r f s) (if r s f) (object-array n) (object-array n) 0 -2)] 31 | (loop [index 0] 32 | (let [join (fn [x] 33 | (aset (.-result j) index x) 34 | (terminated j)) 35 | race (fn [x] 36 | (let [w (.-race j)] 37 | (when (neg? w) 38 | (set! (.-race j) index) 39 | (when (== -1 w) (cancel j)))) 40 | (join x))] 41 | (aset (.-children j) index ((.next i) (if r race join) (if r join race))) 42 | (when (.hasNext i) (recur (inc index))))) 43 | (if (== -2 (.-race j)) 44 | (set! (.-race j) -1) 45 | (cancel j)) j)) -------------------------------------------------------------------------------- /src/missionary/impl/Reduce.cljs: -------------------------------------------------------------------------------- 1 | (ns ^:no-doc missionary.impl.Reduce) 2 | 3 | (deftype Process 4 | [reducer status failure result input 5 | ^boolean busy 6 | ^boolean done] 7 | IFn 8 | (-invoke [_] (input))) 9 | 10 | (defn transfer [^Process p] 11 | (set! (.-result p) 12 | (try 13 | (let [f (.-reducer p) 14 | r (.-result p) 15 | r (if (identical? r p) 16 | (f) (f r @(.-input p)))] 17 | (if (reduced? r) 18 | (do ((.-input p)) (set! (.-reducer p) nil) @r) r)) 19 | (catch :default e 20 | ((.-input p)) (set! (.-reducer p) nil) 21 | (set! (.-status p) (.-failure p)) e)))) 22 | 23 | (defn ready [^Process p] 24 | (loop [] 25 | (when (set! (.-busy p) (not (.-busy p))) 26 | (if (.-done p) 27 | ((.-status p) (.-result p)) 28 | (do (if (nil? (.-reducer p)) 29 | (try @(.-input p) (catch :default _)) 30 | (transfer p)) (recur)))))) 31 | 32 | (defn run [rf flow success failure] 33 | (let [p (->Process rf success failure nil nil true false)] 34 | (set! (.-result p) p) 35 | (set! (.-input p) 36 | (flow #(ready p) 37 | #(do (set! (.-done p) true) (ready p)))) 38 | (transfer p) 39 | (ready p) p)) 40 | -------------------------------------------------------------------------------- /src/missionary/impl/Reductions.cljs: -------------------------------------------------------------------------------- 1 | (ns ^:no-doc missionary.impl.Reductions) 2 | 3 | (declare transfer) 4 | (deftype Process [reducer notifier terminator result input ^boolean busy ^boolean done] 5 | IFn 6 | (-invoke [_] (input)) 7 | IDeref 8 | (-deref [p] (transfer p))) 9 | 10 | (defn ready [^Process p] 11 | (loop [cb nil] 12 | (if (set! (.-busy p) (not (.-busy p))) 13 | (if (.-done p) 14 | (.-terminator p) 15 | (if (nil? (.-reducer p)) 16 | (do (try @(.-input p) (catch :default _)) 17 | (recur cb)) (.-notifier p))) cb))) 18 | 19 | (defn transfer [^Process ps] 20 | (try 21 | (let [f (.-reducer ps) 22 | r (.-result ps) 23 | r (if (identical? r ps) 24 | (f) (f r @(.-input ps)))] 25 | (set! (.-result ps) 26 | (if (reduced? r) 27 | (do ((.-input ps)) 28 | (set! (.-reducer ps) nil) 29 | @r) r))) 30 | (catch :default e 31 | ((.-input ps)) 32 | (set! (.-notifier ps) nil) 33 | (set! (.-reducer ps) nil) 34 | (set! (.-result ps) e))) 35 | (when-some [cb (ready ps)] (cb)) 36 | (if (nil? (.-notifier ps)) 37 | (throw (.-result ps)) 38 | (.-result ps))) 39 | 40 | (defn run [rf f n t] 41 | (let [ps (->Process rf n t nil nil true false)] 42 | (set! (.-result ps) ps) 43 | (set! (.-input ps) 44 | (f #(when-some [cb (ready ps)] (cb)) 45 | #(do (set! (.-done ps) true) 46 | (when-some [cb (ready ps)] 47 | (cb))))) (n) ps)) 48 | -------------------------------------------------------------------------------- /src/missionary/impl/Relieve.cljs: -------------------------------------------------------------------------------- 1 | (ns ^:no-doc missionary.impl.Relieve) 2 | 3 | (declare transfer) 4 | (deftype Process [reducer notifier terminator iterator current ^boolean busy ^boolean done] 5 | IFn 6 | (-invoke [_] (iterator)) 7 | IDeref 8 | (-deref [p] (transfer p))) 9 | 10 | (defn transfer [^Process ps] 11 | (let [x (.-current ps)] 12 | (set! (.-current ps) ps) 13 | (when (nil? (.-reducer ps)) 14 | ((.-terminator ps))) 15 | (if (nil? (.-notifier ps)) 16 | (throw x) x))) 17 | 18 | (defn ready [^Process ps] 19 | (loop [cb nil] 20 | (if (set! (.-busy ps) (not (.-busy ps))) 21 | (recur (if (.-done ps) 22 | (do (set! (.-reducer ps) nil) 23 | (if (identical? ps (.-current ps)) 24 | (.-terminator ps) cb)) 25 | (if-some [n (.-notifier ps)] 26 | (let [r (.-current ps)] 27 | (try (let [x @(.-iterator ps)] 28 | (set! (.-current ps) 29 | (if (identical? r ps) 30 | x ((.-reducer ps) r x)))) 31 | (catch :default e 32 | (set! (.-current ps) e) 33 | (set! (.-notifier ps) nil) 34 | ((.-iterator ps)))) 35 | (if (identical? r ps) n cb)) 36 | (do (try @(.-iterator ps) (catch :default _)) cb)))) cb))) 37 | 38 | (defn run [rf f n t] 39 | (let [ps (->Process rf n t nil nil true false)] 40 | (set! (.-current ps) ps) 41 | (set! (.-iterator ps) 42 | (f #(when-some [cb (ready ps)] (cb)) 43 | #(do (set! (.-done ps) true) 44 | (when-some [cb (ready ps)] (cb))))) 45 | (when-some [cb (ready ps)] (cb)) ps)) -------------------------------------------------------------------------------- /src/missionary/impl/Rendezvous.cljs: -------------------------------------------------------------------------------- 1 | (ns missionary.impl.Rendezvous 2 | (:import missionary.Cancelled)) 3 | 4 | (defn nop []) 5 | 6 | (deftype Port [^:mutable readers 7 | ^:mutable writers] 8 | IFn 9 | (-invoke [_ t] 10 | (fn [s! f!] 11 | (if-some [[!] (seq readers)] 12 | (do (set! readers (disj readers !)) 13 | (! t) (s! nil) nop) 14 | (let [! #(s! nil)] 15 | (set! writers (assoc writers ! t)) 16 | #(when (contains? writers !) 17 | (set! writers (dissoc writers !)) 18 | (f! (Cancelled. "Rendez-vous give cancelled."))))))) 19 | (-invoke [_ s! f!] 20 | (if-some [[[! t]] (seq writers)] 21 | (do (set! writers (dissoc writers !)) 22 | (!) (s! t) nop) 23 | (let [! #(s! %)] 24 | (set! readers (conj readers !)) 25 | #(when (contains? readers !) 26 | (set! readers (disj readers !)) 27 | (f! (Cancelled. "Rendez-vous take cancelled."))))))) 28 | 29 | (defn make [] (->Port #{} {})) 30 | -------------------------------------------------------------------------------- /src/missionary/impl/Sample.cljs: -------------------------------------------------------------------------------- 1 | (ns ^:no-doc missionary.impl.Sample) 2 | 3 | (declare transfer) 4 | 5 | (deftype Process [combinator notifier terminator args inputs 6 | ^boolean busy ^boolean done ^number alive] 7 | IFn 8 | (-invoke [_] (dotimes [i (alength inputs)] ((aget inputs i)))) 9 | IDeref 10 | (-deref [p] (transfer p))) 11 | 12 | (defn ready [^Process ps] 13 | (let [args (.-args ps) 14 | inputs (.-inputs ps) 15 | sampled (dec (alength inputs))] 16 | (loop [cb nil] 17 | (if (set! (.-busy ps) (not (.-busy ps))) 18 | (if (.-done ps) 19 | (do (dotimes [i sampled] 20 | (let [input (aget inputs i)] 21 | (input) 22 | (if (identical? (aget args i) args) 23 | (try @input (catch :default _)) 24 | (aset args i args)))) 25 | (when (zero? (set! (.-alive ps) (dec (.-alive ps)))) 26 | (.-terminator ps))) 27 | (if (identical? (aget args sampled) args) 28 | (do (try @(aget inputs sampled) 29 | (catch :default _)) (recur cb)) 30 | (.-notifier ps))) cb)))) 31 | 32 | (defn transfer [^Process ps] 33 | (let [c (.-combinator ps) 34 | args (.-args ps) 35 | inputs (.-inputs ps) 36 | sampled (dec (alength inputs)) 37 | sampler (aget inputs sampled) 38 | x (try 39 | (try 40 | (when (nil? c) (throw (js/Error. "Undefined continuous flow."))) 41 | (dotimes [i sampled] 42 | (when (identical? (aget args i) args) 43 | (let [input (aget inputs i)] 44 | (loop [] 45 | (aset args i nil) 46 | (let [x @input] 47 | (if (identical? (aget args i) args) 48 | (recur) (aset args i x))))))) 49 | (catch :default e 50 | (try @sampler (catch :default _)) 51 | (throw e))) 52 | (aset args sampled @sampler) 53 | (.apply c nil args) 54 | (catch :default e 55 | (set! (.-notifier ps) nil) 56 | (sampler) 57 | (aset args sampled args) e))] 58 | (when-some [cb (ready ps)] (cb)) 59 | (if (nil? (.-notifier ps)) 60 | (throw x) x))) 61 | 62 | (defn dirty [^Process p ^number i] 63 | (let [args (.-args p)] 64 | (if (identical? (aget args i) args) 65 | (try @(aget (.-inputs p) i) 66 | (catch :default _)) 67 | (aset args i args)))) 68 | 69 | (defn run [c f fs n t] 70 | (let [it (iter fs) 71 | arity (inc (count fs)) 72 | args (object-array arity) 73 | inputs (object-array arity) 74 | ps (->Process c n t args inputs false false arity) 75 | done #(when (zero? (set! (.-alive ps) (dec (.-alive ps)))) 76 | ((.-terminator ps)))] 77 | (loop [index 0 flow f] 78 | (if (.hasNext it) 79 | (do (aset inputs index (flow #(dirty ps index) done)) 80 | (when (nil? (aget args index)) (set! (.-combinator ps) nil)) 81 | (recur (inc index) (.next it))) 82 | (aset inputs index 83 | (flow #(when-some [cb (ready ps)] (cb)) 84 | #(do (set! (.-done ps) true) 85 | (when-some [cb (ready ps)] 86 | (cb))))))) ps)) -------------------------------------------------------------------------------- /src/missionary/impl/Seed.cljs: -------------------------------------------------------------------------------- 1 | (ns missionary.impl.Seed 2 | (:import missionary.Cancelled)) 3 | 4 | (declare cancel transfer) 5 | (deftype Process 6 | [iterator notifier terminator] 7 | IFn 8 | (-invoke [ps] (cancel ps)) 9 | IDeref 10 | (-deref [ps] (transfer ps))) 11 | 12 | (defn cancel [^Process ps] 13 | (set! (.-iterator ps) nil)) 14 | 15 | (defn more [^Process ps i] 16 | (if (.hasNext i) 17 | ((.-notifier ps)) 18 | (do (set! (.-iterator ps) nil) 19 | ((.-terminator ps))))) 20 | 21 | (defn transfer [^Process ps] 22 | (if-some [i (.-iterator ps)] 23 | (let [x (.next i)] 24 | (more ps i) x) 25 | (do ((.-terminator ps)) 26 | (throw (Cancelled. "Seed cancelled"))))) 27 | 28 | (defn run [coll n t] 29 | (let [i (iter coll) 30 | ps (->Process i n t)] 31 | (more ps i) ps)) 32 | -------------------------------------------------------------------------------- /src/missionary/impl/Semaphore.cljs: -------------------------------------------------------------------------------- 1 | (ns missionary.impl.Semaphore 2 | (:import missionary.Cancelled)) 3 | 4 | (defn nop []) 5 | 6 | (deftype Port [^:mutable available 7 | ^:mutable readers] 8 | IFn 9 | (-invoke [_] 10 | (if-some [[!] (seq readers)] 11 | (do (set! readers (disj readers !)) (!)) 12 | (do (set! available (inc available)) nil))) 13 | (-invoke [_ s! f!] 14 | (if (zero? available) 15 | (let [! #(s! nil)] 16 | (set! readers (conj readers !)) 17 | #(when (contains? readers !) 18 | (set! readers (disj readers !)) 19 | (f! (Cancelled. "Semaphore acquire cancelled.")))) 20 | (do (set! available (dec available)) 21 | (s! nil) nop)))) 22 | 23 | (defn make [n] (->Port n #{})) -------------------------------------------------------------------------------- /src/missionary/impl/Sequential.cljs: -------------------------------------------------------------------------------- 1 | (ns ^:no-doc missionary.impl.Sequential 2 | (:require [missionary.impl.Fiber :refer [Fiber fiber]]) 3 | (:import missionary.Cancelled)) 4 | 5 | (defn nop []) 6 | 7 | (declare kill suspend) 8 | 9 | (deftype Process [coroutine success failure resume rethrow ^boolean busy ^boolean failed current token] 10 | IFn 11 | (-invoke [this] (kill this) nil) 12 | Fiber 13 | (park [this task] (suspend this task)) 14 | (swich [_ _] (throw (js/Error. "Unsupported operation."))) 15 | (fork [_ _ _] (throw (js/Error. "Unsupported operation."))) 16 | (check [_] (when (nil? token) (throw (Cancelled. "Process cancelled.")))) 17 | (unpark [this] 18 | (let [x current] 19 | (set! (.-current this) nil) 20 | (when failed 21 | (set! (.-failed this) false) 22 | (throw x)) x))) 23 | 24 | (defn kill [^Process ps] 25 | (when-some [c (.-token ps)] 26 | (set! (.-token ps) nil) (c))) 27 | 28 | (defn suspend [^Process ps task] 29 | (let [c (task (.-resume ps) 30 | (.-rethrow ps))] 31 | (if (nil? (.-token ps)) 32 | (c) (set! (.-token ps) c))) ps) 33 | 34 | (defn step [^Process ps] 35 | (when (set! (.-busy ps) (not (.-busy ps))) 36 | (let [prev fiber] 37 | (set! fiber ps) 38 | (try 39 | (loop [] 40 | (let [x ((.-coroutine ps))] 41 | (if (identical? x ps) 42 | (when (set! (.-busy ps) (not (.-busy ps))) 43 | (recur)) ((.-success ps) x)))) 44 | (catch :default e 45 | ((.-failure ps) e))) 46 | (set! fiber prev)))) 47 | 48 | (defn run [cr s f] 49 | (let [ps (->Process cr s f nil nil false false nil nop)] 50 | (set! (.-resume ps) 51 | (fn [x] 52 | (set! (.-current ps) x) 53 | (step ps) nil)) 54 | (set! (.-rethrow ps) 55 | (fn [e] 56 | (set! (.-failed ps) true) 57 | (set! (.-current ps) e) 58 | (step ps) nil)) 59 | (step ps) ps)) -------------------------------------------------------------------------------- /src/missionary/impl/Sleep.cljs: -------------------------------------------------------------------------------- 1 | (ns missionary.impl.Sleep 2 | (:import missionary.Cancelled)) 3 | 4 | (declare cancel) 5 | (deftype Process 6 | [failure handler 7 | ^boolean pending] 8 | IFn 9 | (-invoke [s] (cancel s))) 10 | 11 | (defn cancel [^Process s] 12 | (when (.-pending s) 13 | (set! (.-pending s) false) 14 | (js/clearTimeout (.-handler s)) 15 | ((.-failure s) (Cancelled. "Sleep cancelled.")))) 16 | 17 | (defn run [d x s f] 18 | (let [slp (->Process f nil true)] 19 | (set! (.-handler slp) (js/setTimeout #(do (set! (.-pending slp) false) (s x)) d)) slp)) 20 | 21 | -------------------------------------------------------------------------------- /src/missionary/impl/Store.cljs: -------------------------------------------------------------------------------- 1 | (ns missionary.impl.Store 2 | (:import missionary.Cancelled)) 3 | 4 | (declare freeze append spawn cancel transfer) 5 | 6 | (deftype Call [done thunk] 7 | IFn 8 | (-invoke [_]) 9 | IDeref 10 | (-deref [_] 11 | (done) (thunk))) 12 | 13 | (deftype Frozen [failed result] 14 | IFn 15 | (-invoke [_] 16 | (if ^boolean failed 17 | (throw result) result))) 18 | 19 | (deftype Port [sg state] 20 | IFn 21 | (-invoke [this] 22 | (freeze this)) 23 | (-invoke [this x] 24 | (append this x)) 25 | (-invoke [this step done] 26 | (spawn this step done))) 27 | 28 | (deftype Reader [port step done state last] 29 | IFn 30 | (-invoke [this] 31 | (cancel this)) 32 | IDeref 33 | (-deref [this] 34 | (transfer this))) 35 | 36 | (def over (js-obj)) 37 | (def zero (js-obj)) 38 | 39 | (defn concurrent [] 40 | (throw (js/Error. "Concurrent store reader."))) 41 | 42 | (defn terminate [^Reader reader last] 43 | (let [^Port port (.-port reader)] 44 | (set! (.-state port) last) 45 | (set! (.-last reader) zero))) 46 | 47 | (defn freeze [^Port port] 48 | (let [s (.-state port)] 49 | (if (instance? Reader s) 50 | (let [^Reader reader s 51 | t (.-state reader)] 52 | (if (identical? t zero) 53 | (do (set! (.-state reader) over) 54 | (terminate reader (->Frozen false (.-last reader))) 55 | ((.-done reader))) 56 | (when-not (instance? Frozen t) 57 | (set! (.-state reader) (->Frozen false t))))) 58 | (when-not (instance? Frozen s) 59 | (set! (.-state port) (->Frozen false s)))))) 60 | 61 | (defn collapse [^Port port x y] 62 | (try ((.-sg port) x y) 63 | (catch :default e 64 | (->Frozen true e)))) 65 | 66 | (defn append [^Port port x] 67 | (let [s (.-state port)] 68 | (if (instance? Reader s) 69 | (let [^Reader reader s 70 | t (.-state reader)] 71 | (if (identical? t zero) 72 | (do (set! (.-state reader) x) 73 | ((.-step reader))) 74 | (when-not (instance? Frozen t) 75 | (set! (.-state reader) (collapse port t x))))) 76 | (when-not (instance? Frozen s) 77 | (set! (.-state port) (collapse port s x)))))) 78 | 79 | (defn accumulate [^Reader reader y] 80 | (let [^Port port (.-port reader) 81 | x (.-last reader)] 82 | (if (identical? zero x) 83 | y ((.-sg port) x y)))) 84 | 85 | (defn cancel [^Reader reader] 86 | (let [t (.-state reader)] 87 | (when-not (identical? over t) 88 | (set! (.-state reader) over) 89 | (if (identical? zero t) 90 | (do (terminate reader (.-last reader)) 91 | ((.-step reader))) 92 | (try (terminate reader 93 | (if (instance? Frozen t) 94 | (->Frozen false (accumulate reader (t))) 95 | (accumulate reader t))) 96 | (catch :default e 97 | (terminate reader 98 | (->Frozen true e)))))))) 99 | 100 | (defn transfer [^Reader reader] 101 | (let [t (.-state reader)] 102 | (if (identical? over t) 103 | (do ((.-done reader)) 104 | (throw (Cancelled. "Store reader cancelled."))) 105 | (try (if (instance? Frozen t) 106 | (let [x (t) 107 | r (accumulate reader x)] 108 | (set! (.-state reader) over) 109 | (terminate reader (->Frozen false r)) 110 | ((.-done reader)) x) 111 | (let [r (accumulate reader t)] 112 | (set! (.-state reader) zero) 113 | (set! (.-last reader) r) t)) 114 | (catch :default e 115 | (set! (.-state reader) over) 116 | (terminate reader (->Frozen true e)) 117 | ((.-done reader)) (throw e)))))) 118 | 119 | (defn spawn [^Port port step done] 120 | (step) 121 | (let [s (.-state port)] 122 | (if (instance? Reader s) 123 | (->Call done concurrent) 124 | (if (instance? Frozen s) 125 | (->Call done s) 126 | (set! (.-state port) (->Reader port step done s zero)))))) 127 | 128 | (def make ->Port) -------------------------------------------------------------------------------- /src/missionary/impl/Watch.cljs: -------------------------------------------------------------------------------- 1 | (ns ^:no-doc missionary.impl.Watch 2 | (:import missionary.Cancelled)) 3 | 4 | (declare kill transfer) 5 | (deftype Process [notifier terminator reference value] 6 | IFn 7 | (-invoke [this] (kill this) nil) 8 | IDeref 9 | (-deref [this] (transfer this))) 10 | 11 | (defn watch [^Process ps _ _ curr] 12 | (when-some [cb (.-notifier ps)] 13 | (let [x (.-value ps)] 14 | (set! (.-value ps) curr) 15 | (when (identical? x ps) (cb))))) 16 | 17 | (defn kill [^Process ps] 18 | (when-some [cb (.-notifier ps)] 19 | (set! (.-notifier ps) nil) 20 | (let [x (.-value ps)] 21 | (set! (.-value ps) nil) 22 | (when (identical? x ps) (cb))))) 23 | 24 | (defn transfer [^Process ps] 25 | (if (nil? (.-notifier ps)) 26 | (do ((.-terminator ps)) 27 | (remove-watch (.-reference ps) ps) 28 | (throw (Cancelled. "Watch cancelled."))) 29 | (let [x (.-value ps)] 30 | (set! (.-value ps) ps) x))) 31 | 32 | (defn run [r n t] 33 | (let [ps (->Process n t r @r)] 34 | (add-watch r ps watch) 35 | (n) ps)) 36 | -------------------------------------------------------------------------------- /src/missionary/impl/Zip.cljs: -------------------------------------------------------------------------------- 1 | (ns missionary.impl.Zip) 2 | 3 | (declare cancel transfer) 4 | (deftype Process [combine step done inputs ^number pending] 5 | IFn 6 | (-invoke [z] (cancel z)) 7 | IDeref 8 | (-deref [z] (transfer z))) 9 | 10 | (defn ready [^Process ps] 11 | (if-some [s (.-step ps)] 12 | (s) (let [inputs (.-inputs ps) 13 | arity (alength inputs)] 14 | (loop [i 0 c 0] 15 | (if (< i arity) 16 | (let [input (aget inputs i)] 17 | (if (identical? input ps) 18 | (recur (inc i) c) 19 | (do (try @input (catch :default _)) 20 | (recur (inc i) (inc c))))) 21 | (let [p (set! (.-pending ps) (+ (.-pending ps) c))] 22 | (if (zero? c) ((.-done ps)) (when (zero? p) (ready ps))))))))) 23 | 24 | (defn cancel [^Process ps] 25 | (let [inputs (.-inputs ps)] 26 | (dotimes [i (alength inputs)] 27 | (let [input (aget inputs i)] 28 | (when-not (identical? input ps) 29 | (input)))))) 30 | 31 | (defn transfer [^Process ps] 32 | (let [c (volatile! 0) 33 | inputs (.-inputs ps) 34 | arity (alength inputs) 35 | buffer (object-array arity)] 36 | (try (dotimes [i arity] 37 | (vswap! c inc) 38 | (aset buffer i @(aget inputs i))) 39 | (.apply (.-combine ps) nil buffer) 40 | (catch :default e 41 | (set! (.-step ps) nil) 42 | (throw e)) 43 | (finally 44 | (let [p (set! (.-pending ps) (+ (.-pending ps) @c))] 45 | (when (nil? (.-step ps)) (cancel ps)) 46 | (when (zero? p) (ready ps))))))) 47 | 48 | (defn run [f fs s d] 49 | (let [arity (count fs) 50 | inputs (object-array arity) 51 | ps (->Process f s d inputs 0) 52 | it (iter fs)] 53 | (loop [i 0] 54 | (let [input ((.next it) 55 | #(let [p (set! (.-pending ps) (dec (.-pending ps)))] 56 | (when (zero? p) (ready ps))) 57 | #(do (aset (.-inputs ps) i ps) 58 | (set! (.-step ps) nil) 59 | (let [p (set! (.-pending ps) (dec (.-pending ps)))] 60 | (when-not (neg? p) (cancel ps)) 61 | (when (zero? p) (ready ps)))))] 62 | (when (nil? (aget inputs i)) (aset inputs i input)) 63 | (when (.hasNext it) (recur (inc i))))) 64 | (let [p (set! (.-pending ps) (+ (.-pending ps) arity))] 65 | (when (nil? (.-step ps)) (cancel ps)) 66 | (when (zero? p) (ready ps)) ps))) 67 | -------------------------------------------------------------------------------- /tck/missionary/PublisherTest.java: -------------------------------------------------------------------------------- 1 | package missionary; 2 | 3 | import clojure.java.api.Clojure; 4 | import clojure.lang.IFn; 5 | import org.reactivestreams.Publisher; 6 | import org.reactivestreams.tck.PublisherVerification; 7 | import org.reactivestreams.tck.TestEnvironment; 8 | 9 | @SuppressWarnings("unchecked") 10 | public final class PublisherTest extends PublisherVerification { 11 | 12 | public PublisherTest() { 13 | super(new TestEnvironment()); 14 | } 15 | 16 | @Override 17 | public Publisher createPublisher(final long n) { 18 | Clojure.var("clojure.core", "require").invoke(Clojure.read("missionary.core")); 19 | IFn range = Clojure.var("clojure.core", "range"); 20 | IFn publisher = Clojure.var("missionary.core", "publisher"); 21 | IFn enumerate = Clojure.var("missionary.core", "seed"); 22 | return (Publisher) publisher.invoke(enumerate.invoke(range.invoke(n))); 23 | } 24 | 25 | @Override 26 | public Publisher createFailedPublisher() { 27 | return null; 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /tck/missionary/SubscriberTest.java: -------------------------------------------------------------------------------- 1 | package missionary; 2 | 3 | import clojure.java.api.Clojure; 4 | import clojure.lang.IFn; 5 | import org.reactivestreams.Publisher; 6 | import org.reactivestreams.Subscriber; 7 | import org.reactivestreams.Subscription; 8 | import org.reactivestreams.tck.SubscriberWhiteboxVerification; 9 | import org.reactivestreams.tck.TestEnvironment; 10 | 11 | public final class SubscriberTest extends SubscriberWhiteboxVerification { 12 | public SubscriberTest() { 13 | super(new TestEnvironment()); 14 | } 15 | 16 | @Override 17 | public Subscriber createSubscriber(final WhiteboxSubscriberProbe probe) { 18 | 19 | return new Subscriber() { 20 | Subscriber subscriber; 21 | 22 | { 23 | Clojure.var("clojure.core", "require").invoke(Clojure.read("missionary.core")); 24 | IFn println = Clojure.var("clojure.core", "println"); 25 | IFn plus = Clojure.var("clojure.core", "+"); 26 | IFn subscribe = Clojure.var("missionary.core", "subscribe"); 27 | IFn sink = Clojure.var ("missionary.core", "reduce"); 28 | IFn task = (IFn) sink.invoke(plus, subscribe.invoke(new Publisher() { 29 | public void subscribe(Subscriber s) { 30 | subscriber = s; 31 | } 32 | })); 33 | task.invoke(println, println); 34 | } 35 | 36 | public void onSubscribe(final Subscription subscription) { 37 | subscriber.onSubscribe(subscription); 38 | probe.registerOnSubscribe(new SubscriberPuppet() { 39 | public void triggerRequest(long elements) { 40 | subscription.request(elements); 41 | } 42 | public void signalCancel() { 43 | subscription.cancel(); 44 | } 45 | }); 46 | } 47 | public void onNext(Object o) { 48 | subscriber.onNext(o); 49 | probe.registerOnNext(o); 50 | } 51 | public void onError(Throwable throwable) { 52 | subscriber.onError(throwable); 53 | probe.registerOnError(throwable); 54 | } 55 | public void onComplete() { 56 | subscriber.onComplete(); 57 | probe.registerOnComplete(); 58 | } 59 | }; 60 | } 61 | 62 | @Override 63 | public Object createElement(int element) { 64 | return element; 65 | } 66 | 67 | } 68 | 69 | -------------------------------------------------------------------------------- /test/missionary/absolve_test.cljc: -------------------------------------------------------------------------------- 1 | (ns missionary.absolve-test 2 | (:require [lolcat.core :as lc] 3 | [lolcat.lib :as l] 4 | [missionary.core :as m] 5 | [clojure.test :as t])) 6 | 7 | (t/deftest simple 8 | (t/is (= [] 9 | (lc/run 10 | (l/store 11 | (m/absolve (l/task :input)) 12 | (l/start :main (l/started :input)) 13 | (l/succeed :input #(do 1) 14 | (l/succeeded :main #{1}))))))) 15 | 16 | (t/deftest input-fails 17 | (t/is (= [] 18 | (lc/run 19 | (l/store 20 | (m/absolve (l/task :input)) 21 | (l/start :main (l/started :input)) 22 | (l/fail :input 2 23 | (l/failed :main #{2}))))))) 24 | 25 | (t/deftest cancel 26 | (t/is (= [] 27 | (lc/run 28 | (l/store 29 | (m/absolve (l/task :input)) 30 | (l/start :main (l/started :input)) 31 | (l/cancel :main 32 | (l/cancelled :input))))))) 33 | -------------------------------------------------------------------------------- /test/missionary/ap_test.cljc: -------------------------------------------------------------------------------- 1 | (ns missionary.ap-test 2 | (:require [lolcat.core :as lc] 3 | [lolcat.lib :as l] 4 | [missionary.core :as m] 5 | [clojure.test :as t])) 6 | 7 | (t/deftest ? 8 | (t/is (= [] 9 | (lc/run 10 | (l/store 11 | (m/ap (m/? (l/task :input))) 12 | (l/spawn :main (l/started :input)) 13 | (l/succeed :input 1 (l/notified :main)) 14 | (l/transfer :main (l/terminated :main)) 15 | (l/check #{1})))))) 16 | 17 | (def err (ex-info "" {})) 18 | (t/deftest ?-cancel-with-failure 19 | (t/is (= [] 20 | (lc/run 21 | (l/store 22 | (m/ap (m/? (l/task :input))) 23 | (l/spawn :main (l/started :input)) 24 | (l/cancel :main (l/cancelled :input)) 25 | (l/fail :input err (l/notified :main)) 26 | (l/crash :main (l/terminated :main)) 27 | (l/check #{err})))))) 28 | 29 | (t/deftest ?> 30 | (t/is (= [] 31 | (lc/run 32 | (l/store 33 | (m/ap (m/?> (l/flow :input))) 34 | (l/spawn :main (l/spawned :input)) 35 | (l/notify :input 36 | (l/transferred :input 1) 37 | (l/notified :main)) 38 | (l/notify :input) 39 | (l/transfer :main 40 | (l/transferred :input 2) 41 | (l/notified :main)) 42 | (l/check #{1}) 43 | (l/transfer :main) 44 | (l/check #{2}) 45 | (l/terminate :input (l/terminated :main))))))) 46 | 47 | (t/deftest ?>-cancel-with-crash 48 | (t/is (= [] 49 | (lc/run 50 | (l/store 51 | (m/ap (m/?> (l/flow :input))) 52 | (l/spawn :main (l/spawned :input)) 53 | (l/cancel :main (l/cancelled :input)) 54 | (l/notify :input 55 | (l/crashed :input err) 56 | (l/notified :main)) 57 | (l/crash :main) 58 | (l/check #{err}) 59 | (l/terminate :input (l/terminated :main))))))) 60 | 61 | (t/deftest ?>-parallel 62 | (t/is (= [] 63 | (lc/run 64 | (l/store 65 | (m/ap (m/?> 3 (l/flow :input))) 66 | (l/spawn :main (l/spawned :input)) 67 | (l/notify :input 68 | (l/transferred :input 1) 69 | (l/notified :main)) 70 | (l/notify :input (l/transferred :input 2)) 71 | (l/notify :input (l/transferred :input 3)) 72 | (l/notify :input) 73 | (l/transfer= 1 :main 74 | (l/transferred :input 4) 75 | (l/notified :main)) 76 | (l/transfer= 2 :main (l/notified :main)) 77 | (l/transfer= 3 :main (l/notified :main)) 78 | (l/transfer= 4 :main) 79 | (l/terminate :input (l/terminated :main))))))) 80 | 81 | (lc/defword handle [evt v] [(l/check #{evt}) v]) 82 | 83 | (t/deftest amb 84 | (t/is (= [] 85 | (lc/run 86 | (l/store 87 | (m/ap (m/amb (lc/event :first) (lc/event :second))) 88 | (l/spawn :main 89 | (handle :first 1) 90 | (l/notified :main)) 91 | (l/transfer= 1 :main 92 | (handle :second 2) 93 | (l/notified :main)) 94 | (l/transfer= 2 :main 95 | (l/terminated :main))))))) 96 | 97 | (t/deftest amb= 98 | (t/is (= [] 99 | (lc/run 100 | (l/store 101 | (m/ap (m/amb= (lc/event :e) (lc/event :e))) 102 | (l/spawn :main 103 | (handle :e 1) 104 | (l/notified :main) 105 | (handle :e 1)) 106 | (l/transfer= 1 :main 107 | (l/notified :main)) 108 | (l/transfer= 1 :main 109 | (l/terminated :main))))))) 110 | -------------------------------------------------------------------------------- /test/missionary/attempt_test.cljc: -------------------------------------------------------------------------------- 1 | (ns missionary.attempt-test 2 | (:require [lolcat.core :as lc] 3 | [lolcat.lib :as l] 4 | [missionary.core :as m] 5 | [clojure.test :as t]) 6 | #?(:clj (:import [clojure.lang ExceptionInfo]))) 7 | 8 | (t/deftest success 9 | (t/is (= [] 10 | (lc/run 11 | (l/store 12 | (m/attempt (l/task :input)) 13 | (l/start :main (l/started :input)) 14 | (l/succeed :input 1 15 | (l/succeeded :main #(= 1 (%))))))))) 16 | 17 | (def err (ex-info "" {})) 18 | (t/deftest failure 19 | (t/is (= [] 20 | (lc/run 21 | (l/store 22 | (m/attempt (l/task :input)) 23 | (l/start :main (l/started :input)) 24 | (l/fail :input err 25 | (l/succeeded :main #(= err (try (%) (catch ExceptionInfo e e)))))))))) 26 | 27 | (t/deftest cancel 28 | (t/is (= [] 29 | (lc/run 30 | (l/store 31 | (m/attempt (l/task :input)) 32 | (l/start :main (l/started :input)) 33 | (l/cancel :main 34 | (l/cancelled :input))))))) 35 | -------------------------------------------------------------------------------- /test/missionary/buffer_test.cljc: -------------------------------------------------------------------------------- 1 | (ns missionary.buffer-test 2 | (:require [lolcat.core :as lc] 3 | [lolcat.lib :as l] 4 | [missionary.core :as m] 5 | [clojure.test :as t])) 6 | 7 | (t/deftest simple-with-cancel 8 | (t/is (= [] 9 | (lc/run 10 | (l/store 11 | (m/buffer 2 (l/flow :input)) 12 | (l/spawn :main 13 | (l/spawned :input)) 14 | (l/notify :input 15 | (l/transferred :input 0) 16 | (l/notified :main)) 17 | (l/notify :input 18 | ;; buffer=2 and we have 1, so we transfer again 19 | (l/transferred :input 1)) 20 | (l/notify :input 21 | ;; buffer full, no more transfer 22 | ) 23 | (l/transfer :main 24 | ;; buffer has space and we were notified, so we transfer again 25 | (l/transferred :input 2) 26 | (l/notified :main)) 27 | (l/check #{0}) 28 | (l/transfer :main 29 | (l/notified :main)) 30 | (l/check #{1}) 31 | (l/transfer :main) 32 | (l/check #{2}) 33 | (l/cancel :main 34 | (l/cancelled :input))))))) 35 | 36 | (t/deftest input-terminates 37 | (t/is (= [] 38 | (lc/run 39 | (l/store 40 | (m/buffer 2 (l/flow :input)) 41 | (l/spawn :main 42 | (l/spawned :input)) 43 | (l/terminate :input 44 | (l/terminated :main)))))) 45 | (t/testing "but there's still value to transfer" 46 | (t/is (= [] 47 | (lc/run 48 | (l/store 49 | (m/buffer 2 (l/flow :input)) 50 | (l/spawn :main 51 | (l/spawned :input)) 52 | (l/notify :input 53 | (l/transferred :input 1) 54 | (l/notified :main)) 55 | (l/terminate :input) 56 | (l/transfer :main 57 | (l/terminated :main)) 58 | (l/check #{1}))))))) 59 | 60 | (def err (ex-info "" {})) 61 | 62 | (t/deftest input-crashes 63 | (t/is (= [] 64 | (lc/run 65 | (l/store 66 | (m/buffer 2 (l/flow :input)) 67 | (l/spawn :main 68 | (l/spawned :input)) 69 | (l/notify :input 70 | (l/transferred :input 0) 71 | (l/notified :main)) 72 | (l/notify :input 73 | (l/crashed :input err)) 74 | (l/transfer :main 75 | (l/notified :main)) 76 | (l/check #{0}) 77 | (l/crash :main) 78 | (l/check #{err})))))) 79 | -------------------------------------------------------------------------------- /test/missionary/compel_test.cljc: -------------------------------------------------------------------------------- 1 | (ns missionary.compel-test 2 | (:require [lolcat.core :as lc] 3 | [lolcat.lib :as l] 4 | [missionary.core :as m] 5 | [clojure.test :as t])) 6 | 7 | (t/deftest success 8 | (t/is (= [] 9 | (lc/run 10 | (l/store 11 | (m/compel (l/task :input)) 12 | (l/start :main (l/started :input)) 13 | (l/cancel :main) ; didn't cancel :input 14 | (l/succeed :input 1 15 | (l/succeeded :main #{1}))))))) 16 | 17 | (t/deftest failure 18 | (t/is (= [] 19 | (lc/run 20 | (l/store 21 | (m/compel (l/task :input)) 22 | (l/start :main (l/started :input)) 23 | (l/cancel :main) ; didn't cancel :input 24 | (l/fail :input 1 25 | (l/failed :main #{1}))))))) 26 | -------------------------------------------------------------------------------- /test/missionary/dfv_test.cljc: -------------------------------------------------------------------------------- 1 | (ns missionary.dfv-test 2 | (:require [lolcat.core :as lc] 3 | [lolcat.lib :as l] 4 | [missionary.core :as m] 5 | [clojure.test :as t]) 6 | (:import [missionary Cancelled])) 7 | 8 | (lc/defword assign [v & events] 9 | [v (l/swap) (apply lc/call 1 events) (l/lose)]) 10 | 11 | (t/deftest success 12 | (t/testing "value ready" 13 | (t/is (= [] 14 | (lc/run 15 | (l/store 16 | (m/dfv) 17 | (l/dup) (assign :fine) 18 | (l/start :main 19 | (l/succeeded :main #{:fine}))))))) 20 | (t/testing "value not ready" 21 | (t/is (= [] 22 | (lc/run 23 | (l/store 24 | (m/dfv) (l/dup) (l/-rot) ; dfv store dfv 25 | (l/start :main) 26 | (l/swap) (assign :fine (l/succeeded :main #{:fine}))))))) 27 | (t/testing "only first assignment counts" 28 | (t/is (= [] 29 | (lc/run 30 | (l/store 31 | (m/dfv) 32 | (l/dup) (l/dup) (assign :first) (assign :second) 33 | (l/start :main 34 | (l/succeeded :main #{:first})))))))) 35 | 36 | (t/deftest cancel 37 | (t/is (= [] 38 | (lc/run 39 | (l/store 40 | (m/dfv) 41 | (l/start :main) 42 | (l/cancel :main 43 | (l/failed :main (partial instance? Cancelled)))))))) 44 | -------------------------------------------------------------------------------- /test/missionary/eduction_test.cljc: -------------------------------------------------------------------------------- 1 | (ns missionary.eduction-test 2 | (:require [lolcat.core :as lc] 3 | [lolcat.lib :as l] 4 | [missionary.core :as m] 5 | [clojure.test :as t])) 6 | 7 | (lc/defword init [flow] [flow (l/spawn :main (l/spawned :input))]) 8 | 9 | (t/deftest cancel 10 | (t/is (= [] 11 | (lc/run 12 | (l/store 13 | (init (m/eduction (map inc) (l/flow :input))) 14 | (l/notify :input 15 | ;; as input notifies of a value eduction transfers is and applies xf, 16 | ;; notifying of new value 17 | (l/transferred :input 18 | 0) 19 | (l/notified :main)) 20 | (l/transfer :main) 21 | (l/check #{1}) 22 | (l/cancel :main 23 | ;; eduction is in charge of input, so it cancels it 24 | (l/cancelled :input))))))) 25 | 26 | (t/deftest terminate 27 | (t/is (= [] 28 | (lc/run 29 | (l/store 30 | (init (m/eduction (map inc) (l/flow :input))) 31 | (l/terminate :input 32 | ;; if input terminates, so do we 33 | (l/terminated :main))))))) 34 | 35 | (def err (ex-info "" {})) 36 | 37 | (t/deftest crash-input 38 | (t/is (= [] 39 | (lc/run 40 | (l/store 41 | (init (m/eduction (map inc) (l/flow :input))) 42 | (l/notify :input 43 | ;; input crashing means we have to cancel it and notify of new value 44 | (l/crashed :input 45 | err) 46 | (l/cancelled :input) 47 | (l/notified :main)) 48 | (l/crash :main) 49 | (l/check #{err}) 50 | (l/cancel :main 51 | (l/cancelled :input))))))) 52 | 53 | (t/deftest transducer-throws 54 | (t/is (= [] 55 | (lc/run 56 | (l/store 57 | (init (m/eduction (map (fn [_] (throw err))) (l/flow :input))) 58 | (l/notify :input 59 | (l/transferred :input 60 | 0) 61 | ;; the eduction's xf throws, so it cancels input 62 | (l/cancelled :input) 63 | (l/notified :main)) 64 | (l/crash :main) 65 | (l/check #{err})))))) 66 | 67 | (t/deftest transducer-terminates-early 68 | (t/is (= [] 69 | (lc/run 70 | (l/store 71 | (init (m/eduction (take 1) (l/flow :input))) 72 | (l/notify :input 73 | (l/transferred :input 0) 74 | ;; the xf terminates (via `reduced`), so it cancels input 75 | (l/cancelled :input) 76 | (l/notified :main)) 77 | (l/transfer :main) 78 | (l/check #{0})))))) 79 | 80 | (t/deftest transducer-returns-multiple-values 81 | (t/is (= [] 82 | (lc/run 83 | (l/store 84 | (init (m/eduction cat (l/flow :input))) 85 | (l/notify :input 86 | (l/transferred :input [1 2]) 87 | (l/notified :main)) 88 | (l/transfer :main 89 | ;; we get a new notification, since `cat` returned 2 values 90 | (l/notified :main)) 91 | (l/check #{1}) 92 | (l/transfer :main) 93 | (l/check #{2})))))) 94 | 95 | (t/deftest transducer-swallows-values 96 | (t/is (= [] 97 | (lc/run 98 | (l/store 99 | (init (m/eduction (filter odd?) (l/flow :input))) 100 | (l/notify :input 101 | (l/transferred :input 0) 102 | ;; xf filters this value, so we don't notify from main 103 | ) 104 | (l/notify :input 105 | (l/transferred :input 1) 106 | ;; xf keeps value, so we notify 107 | (l/notified :main)) 108 | (l/transfer :main) 109 | (l/check #{1})))))) 110 | 111 | (t/deftest transducer-produces-on-completion 112 | (t/is (= [] 113 | (lc/run 114 | (l/store 115 | (init (m/eduction (partition-all 2) (l/flow :input))) 116 | (l/notify :input 117 | (l/transferred :input 1)) 118 | (l/terminate :input 119 | ;; input terminated, so partition-all completes with [1], 120 | ;; so it notifies of the last value 121 | (l/notified :main)) 122 | (l/transfer :main 123 | ;; since this was the last value we terminate 124 | (l/terminated :main)) 125 | (l/check #{[1]})))))) 126 | -------------------------------------------------------------------------------- /test/missionary/join_test.cljc: -------------------------------------------------------------------------------- 1 | (ns missionary.join-test 2 | (:require [lolcat.core :as lc] 3 | [lolcat.lib :as l] 4 | [missionary.core :as m] 5 | [clojure.test :as t])) 6 | 7 | (t/deftest success 8 | (t/is (= [] 9 | (lc/run 10 | (l/store 11 | (m/join vector (l/task :a) (l/task :b)) 12 | (l/start :main (l/started :a) (l/started :b)) 13 | (l/succeed :a 1) 14 | (l/succeed :b 2 15 | (l/succeeded :main #{[1 2]}))))))) 16 | 17 | (def err1 (ex-info "1" {})) 18 | (def err2 (ex-info "2" {})) 19 | (t/deftest a-fails 20 | (t/is (= [] 21 | (lc/run 22 | (l/store 23 | (m/join vector (l/task :a) (l/task :b)) 24 | (l/start :main (l/started :a) (l/started :b)) 25 | (l/fail :a err1 26 | (l/cancelled :a) 27 | (l/cancelled :b)) 28 | (l/fail :b err2 29 | (l/failed :main #{err1}))))))) 30 | 31 | (t/deftest b-fails-after-a-succeeded 32 | (t/is (= [] 33 | (lc/run 34 | (l/store 35 | (m/join vector (l/task :a) (l/task :b)) 36 | (l/start :main (l/started :a) (l/started :b)) 37 | (l/succeed :a 1) 38 | (l/fail :b err2 39 | (l/cancelled :a) 40 | (l/cancelled :b) 41 | (l/failed :main #{err2}))))))) 42 | (t/deftest f-throws 43 | (t/is (= [] 44 | (lc/run 45 | (l/store 46 | (m/join (fn [_ _] (throw err1)) (l/task :a) (l/task :b)) 47 | (l/start :main (l/started :a) (l/started :b)) 48 | (l/succeed :a 1) 49 | (l/succeed :b 2 50 | (l/failed :main #{err1}))))))) 51 | 52 | (t/deftest cancel 53 | (t/is (= [] 54 | (lc/run 55 | (l/store 56 | (m/join vector (l/task :a) (l/task :b)) 57 | (l/start :main (l/started :a) (l/started :b)) 58 | (l/cancel :main 59 | (l/cancelled :a) 60 | (l/cancelled :b)) 61 | (l/fail :a err1 62 | (l/cancelled :a) 63 | (l/cancelled :b)) 64 | (l/fail :b err2 65 | (l/failed :main #{err1}))))))) 66 | -------------------------------------------------------------------------------- /test/missionary/latest_test.cljc: -------------------------------------------------------------------------------- 1 | (ns missionary.latest-test 2 | (:require [lolcat.core :as lc] 3 | [lolcat.lib :as l] 4 | [missionary.core :as m] 5 | [clojure.test :as t])) 6 | 7 | (lc/defword init [flow] 8 | [flow (l/spawn :main 9 | (l/spawned :x (l/notify :x)) 10 | (l/spawned :y (l/notify :y)) 11 | (l/notified :main))]) 12 | 13 | (t/deftest simple-with-cancel 14 | (t/is (= [] 15 | (lc/run 16 | (l/store 17 | (init (m/latest vector (l/flow :x) (l/flow :y))) 18 | (l/transfer :main 19 | (l/transferred :x :x1) 20 | (l/transferred :y :y1)) 21 | (l/check #{[:x1 :y1]}) 22 | (l/cancel :main 23 | (l/cancelled :x) 24 | (l/cancelled :y))))))) 25 | 26 | (t/deftest latest-is-retained 27 | (t/is (= [] 28 | (lc/run 29 | (l/store 30 | (init (m/latest vector (l/flow :x) (l/flow :y))) 31 | (l/transfer :main 32 | (l/transferred :x :x1) 33 | (l/transferred :y :y1)) 34 | (l/check #{[:x1 :y1]}) 35 | (l/notify :x 36 | (l/notified :main)) 37 | (l/transfer :main 38 | (l/transferred :x :x2)) 39 | (l/check #{[:x2 :y1]})))))) 40 | 41 | (t/deftest consecutive-notify-causes-retransfer 42 | (t/is (= [] 43 | (lc/run 44 | (l/store 45 | (init (m/latest vector (l/flow :x) (l/flow :y))) 46 | (l/transfer :main 47 | (l/transferred :x (l/notify :x) :x1) 48 | (l/transferred :x :x2) 49 | (l/transferred :y :y1)) 50 | (l/check #{[:x2 :y1]})))))) 51 | 52 | (def err (ex-info "" {})) 53 | 54 | (t/deftest input-crashes 55 | (t/is (= [] 56 | (lc/run 57 | (l/store 58 | (init (m/latest vector (l/flow :x) (l/flow :y))) 59 | (l/crash :main 60 | (l/crashed :x err) 61 | (l/cancelled :x) 62 | (l/cancelled :y) 63 | (l/transferred :y :y1)) 64 | (l/check #{err})))))) 65 | 66 | (lc/defword f-called [] 67 | [(l/check #{:f}) nil]) 68 | 69 | (t/deftest f-crashes 70 | (t/is (= [] 71 | (lc/run 72 | (l/store 73 | (init (m/latest (fn [& _] (lc/event :f) (throw err)) (l/flow :x) (l/flow :y))) 74 | (l/crash :main 75 | (l/transferred :x :x1) 76 | (l/transferred :y :y1) 77 | (f-called) 78 | (l/cancelled :x) 79 | (l/cancelled :y)) 80 | (l/check #{err})))))) 81 | 82 | (t/deftest input-terminates 83 | (t/is (= [] 84 | (lc/run 85 | (l/store 86 | (init (m/latest vector (l/flow :x) (l/flow :y))) 87 | (l/transfer :main 88 | (l/transferred :x :x1) 89 | (l/transferred :y :y1)) 90 | (l/check #{[:x1 :y1]}) 91 | (l/terminate :x) 92 | (l/terminate :y 93 | (l/terminated :main))))))) 94 | 95 | (t/deftest input-change-from-combinator 96 | (t/is (= [] 97 | (lc/run 98 | (l/store 99 | (m/latest lc/event (l/flow :x)) 100 | (l/spawn :main 101 | (l/spawned :x (l/notify :x)) 102 | (l/notified :main)) 103 | (l/transfer :main 104 | (l/transferred :x :x1) 105 | (l/compose 106 | (l/check #{:x1}) 107 | (l/notify :x) 108 | nil) 109 | (l/transferred :x :x2) 110 | (l/compose 111 | (l/check #{:x2}) 112 | :res)) 113 | (l/check #{:res})))))) -------------------------------------------------------------------------------- /test/missionary/mbx_test.cljc: -------------------------------------------------------------------------------- 1 | (ns missionary.mbx-test 2 | (:require [lolcat.core :as lc] 3 | [lolcat.lib :as l] 4 | [missionary.core :as m] 5 | [clojure.test :as t]) 6 | (:import [missionary Cancelled])) 7 | 8 | (lc/defword post [id v & events] [v (l/over) (l/change id) (apply lc/call 1 events) (l/lose)]) 9 | (lc/defword fetch [id task-id & events] [(l/dup) (l/change id) (apply l/start task-id events)]) 10 | 11 | (t/deftest success 12 | (t/testing "post fetch" 13 | (t/is (= [] 14 | (lc/run 15 | (l/store 16 | (m/mbx) (l/insert :mbx) 17 | (post :mbx 1) 18 | (fetch :mbx :fetch (l/succeeded :fetch #{1}))))))) 19 | (t/testing "fetch post" 20 | (t/is (= [] 21 | (lc/run 22 | (l/store 23 | (m/mbx) (l/insert :mbx) 24 | (fetch :mbx :fetch) 25 | (post :mbx 1 (l/succeeded :fetch #{1}))))))) 26 | (t/testing "post post fetch fetch" 27 | (t/is (= [] 28 | (lc/run 29 | (l/store 30 | (m/mbx) (l/insert :mbx) 31 | (post :mbx 1) 32 | (post :mbx 2) 33 | (fetch :mbx :fetch (l/succeeded :fetch #{1})) 34 | (fetch :mbx :fetch (l/succeeded :fetch #{2}))))))) 35 | ;; this case is non-deterministic for now 36 | #_(t/testing "fetch fetch post post" 37 | (t/is (= [] 38 | (lc/run 39 | (l/store 40 | (m/mbx) (l/insert :mbx) 41 | (fetch :mbx :fetch1) 42 | (fetch :mbx :fetch2) 43 | (post :mbx 1 (l/succeeded :fetch1 #{1})) 44 | (post :mbx 2 (l/succeeded :fetch2 #{2})))))))) 45 | 46 | (t/deftest cancel 47 | (t/is (= [] 48 | (lc/run 49 | (l/store 50 | (m/mbx) (l/insert :mbx) 51 | (fetch :mbx :fetch) 52 | (l/cancel :fetch (l/failed :fetch (partial instance? Cancelled)))))))) 53 | -------------------------------------------------------------------------------- /test/missionary/never_test.cljc: -------------------------------------------------------------------------------- 1 | (ns missionary.never-test 2 | (:require [lolcat.core :as lc] 3 | [lolcat.lib :as l] 4 | [missionary.core :as m] 5 | [clojure.test :as t]) 6 | (:import [missionary Cancelled])) 7 | 8 | (t/deftest simple 9 | (t/is (= [] 10 | (lc/run 11 | (l/store 12 | m/never 13 | (l/start :main) 14 | (l/cancel :main 15 | (l/failed :main (partial instance? Cancelled)))))))) 16 | -------------------------------------------------------------------------------- /test/missionary/none_test.cljc: -------------------------------------------------------------------------------- 1 | (ns missionary.none-test 2 | (:require [lolcat.core :as lc] 3 | [lolcat.lib :as l] 4 | [missionary.core :as m] 5 | [clojure.test :as t])) 6 | 7 | (t/deftest none 8 | (t/is (= [] 9 | (lc/run 10 | (l/store 11 | m/none 12 | (l/spawn :main 13 | (l/terminated :main)) 14 | (l/cancel :main)))))) 15 | -------------------------------------------------------------------------------- /test/missionary/observe_test.cljc: -------------------------------------------------------------------------------- 1 | (ns missionary.observe-test 2 | (:require [lolcat.core :as lc] 3 | [lolcat.lib :as l] 4 | [missionary.core :as m] 5 | [clojure.test :as t]) 6 | (:import [missionary Cancelled])) 7 | 8 | (t/deftest a-subject-that-is-immediately-ready 9 | (t/is (= [] 10 | (lc/run 11 | (l/store 12 | (m/observe (fn [f] (f nil) #(do))) 13 | (l/spawn :main 14 | (l/notified :main)) 15 | (l/transfer :main) 16 | (l/check nil?) 17 | (l/cancel :main 18 | (l/notified :main)) 19 | (l/crash :main 20 | (l/terminated :main)) 21 | (l/check #(instance? Cancelled %))))))) 22 | 23 | (defn interrupt-current! [] 24 | #?(:clj (.interrupt (Thread/currentThread)))) 25 | 26 | (t/deftest overflow 27 | (t/is (= [] 28 | (lc/run 29 | (l/store 30 | (m/observe lc/event) 31 | (l/spawn :main 32 | (l/compose 33 | (l/insert :f) 34 | #(do))) 35 | (l/signal :f :x (l/notified :main)) 36 | interrupt-current! 37 | (lc/call 0) 38 | (lc/drop 0) 39 | (l/signal-error :f :x)))))) 40 | 41 | ;; unsubscribe fn is called after cancellation 42 | ;; we don't know when exactly should the unsubscription happen 43 | (t/deftest cancellation 44 | (t/is (= [] 45 | (lc/run 46 | (l/store 47 | (m/observe lc/event) 48 | (l/spawn :main 49 | (l/compose 50 | (lc/drop 0) 51 | #(lc/event :unsub))) 52 | (l/cancel :main 53 | (l/compose 54 | (l/check #{:unsub}) 55 | nil) 56 | (l/notified :main)) 57 | (l/crash :main 58 | (l/terminated :main)) 59 | (l/check #(instance? Cancelled %))))))) 60 | 61 | ;; subject throws exception after callback 62 | (t/deftest subject-callback-throw 63 | (t/is (= [] 64 | (lc/run 65 | (l/store 66 | (m/observe (fn [!] (! :foo) (assert false))) 67 | (l/spawn :main 68 | (l/notified :main)) 69 | (l/crash :main 70 | (l/terminated :main)) 71 | (l/check #(instance? #?(:clj Error :cljs js/Error) %))))))) -------------------------------------------------------------------------------- /test/missionary/pub_test.clj: -------------------------------------------------------------------------------- 1 | (ns missionary.pub-test 2 | (:require [lolcat.core :as lc] 3 | [lolcat.lib :as l] 4 | [missionary.core :as m] 5 | [clojure.test :as t]) 6 | (:import (org.reactivestreams Publisher Subscriber Subscription))) 7 | 8 | (defn subscriber [id] 9 | (reify Subscriber 10 | (onSubscribe [_ s] (lc/event [:on-subscribe id s])) 11 | (onNext [_ x] (lc/event [:on-next id x])) 12 | (onComplete [_] (lc/event [:on-complete id])) 13 | (onError [_ e] (lc/event [:on-error id e])))) 14 | 15 | (defn subscribe! [^Publisher publisher ^Subscriber subscriber] 16 | (.subscribe publisher subscriber)) 17 | 18 | (defn request! [^Subscription subscription n] 19 | (.request subscription (long n))) 20 | 21 | (defn cancel! [^Subscription subscription] 22 | (.cancel subscription)) 23 | 24 | (lc/defword subscribe [id & events] 25 | [(subscriber id) 26 | subscribe! 27 | (apply lc/call 2 events) 28 | (lc/drop 0)]) 29 | 30 | (lc/defword subscribed [id & insts] 31 | (-> [(l/bi peek pop) 32 | (l/check #{[:on-subscribe id]}) 33 | (l/insert id)] 34 | (into insts) 35 | (conj nil))) 36 | 37 | (lc/defword nexted [id & insts] 38 | (-> [(l/bi peek pop) 39 | (l/check #{[:on-next id]})] 40 | (into insts) 41 | (conj nil))) 42 | 43 | (lc/defword completed [id & insts] 44 | (-> [(l/check #{[:on-complete id]})] 45 | (into insts) 46 | (conj nil))) 47 | 48 | (lc/defword errored [id & insts] 49 | (-> [(l/bi peek pop) 50 | (l/check #{[:on-error id]})] 51 | (into insts) 52 | (conj nil))) 53 | 54 | (lc/defword cancel [id & events] 55 | [(lc/copy 0) 56 | (l/change get id) 57 | cancel! 58 | (apply lc/call 1 events) 59 | (lc/drop 0)]) 60 | 61 | (lc/defword request [id n & events] 62 | [(lc/copy 0) 63 | (l/change get id) 64 | n request! 65 | (apply lc/call 2 events) 66 | (lc/drop 0)]) 67 | 68 | (t/deftest empty-flow 69 | (t/is (= [] 70 | (lc/run 71 | (l/store 72 | (m/publisher (l/flow :input)) 73 | (subscribe :main 74 | (l/spawned :input) 75 | (subscribed :main)) 76 | (l/terminate :input 77 | (completed :main))))))) 78 | 79 | (t/deftest request-before-ready 80 | (t/is (= [] 81 | (lc/run 82 | (l/store 83 | (m/publisher (l/flow :input)) 84 | (subscribe :main 85 | (l/spawned :input) 86 | (subscribed :main)) 87 | (request :main 1) 88 | (l/notify :input 89 | (l/transferred :input :foo) 90 | (nexted :main 91 | (l/check #{:foo}))) 92 | (l/terminate :input 93 | (completed :main))))))) 94 | 95 | (t/deftest request-after-ready 96 | (t/is (= [] 97 | (lc/run 98 | (l/store 99 | (m/publisher (l/flow :input)) 100 | (subscribe :main 101 | (l/spawned :input) 102 | (subscribed :main)) 103 | (l/notify :input 104 | (l/transferred :input 105 | (l/notify :input) 106 | :foo)) 107 | (request :main 1 108 | (nexted :main 109 | (l/check #{:foo})) 110 | (l/transferred :input 111 | (l/terminate :input) 112 | :bar)) 113 | (request :main 1 114 | (nexted :main 115 | (l/check #{:bar})) 116 | (completed :main))))))) 117 | 118 | (def err (Error.)) 119 | 120 | (t/deftest failure 121 | (t/is (= [] 122 | (lc/run 123 | (l/store 124 | (m/publisher (l/flow :input)) 125 | (subscribe :main 126 | (l/spawned :input) 127 | (subscribed :main)) 128 | (l/notify :input 129 | (l/crashed :input err)) 130 | (l/terminate :input 131 | (errored :main 132 | (l/check #{err})))))))) 133 | 134 | (t/deftest cancel-waiting 135 | (t/is (= [] 136 | (lc/run 137 | (l/store 138 | (m/publisher (l/flow :input)) 139 | (subscribe :main 140 | (l/spawned :input) 141 | (subscribed :main)) 142 | (cancel :main 143 | (l/cancelled :input 144 | (l/notify :input 145 | (l/crashed :input 146 | (l/terminate :input) 147 | err))))))))) 148 | 149 | (t/deftest cancel-ready 150 | (t/is (= [] 151 | (lc/run 152 | (l/store 153 | (m/publisher (l/flow :input)) 154 | (subscribe :main 155 | (l/spawned :input) 156 | (subscribed :main)) 157 | (l/notify :input 158 | (l/transferred :input 159 | (l/notify :input) 160 | :foo)) 161 | (cancel :main 162 | (l/cancelled :input) 163 | (l/crashed :input 164 | (l/terminate :input) 165 | err))))))) 166 | 167 | (t/deftest cancel-crashed 168 | (t/is (= [] 169 | (lc/run 170 | (l/store 171 | (m/publisher (l/flow :input)) 172 | (subscribe :main 173 | (l/spawned :input) 174 | (subscribed :main)) 175 | (l/notify :input 176 | (l/crashed :input 177 | err)) 178 | (cancel :main 179 | (l/cancelled :input)) 180 | (l/terminate :input)))))) -------------------------------------------------------------------------------- /test/missionary/race_test.cljc: -------------------------------------------------------------------------------- 1 | (ns missionary.race-test 2 | (:require [lolcat.core :as lc] 3 | [lolcat.lib :as l] 4 | [missionary.core :as m] 5 | [clojure.test :as t])) 6 | 7 | (def err1 (ex-info "1" {})) 8 | (def err2 (ex-info "2" {})) 9 | (t/deftest success 10 | (t/is (= [] 11 | (lc/run 12 | (l/store 13 | (m/race (l/task :a) (l/task :b)) 14 | (l/start :main (l/started :a) (l/started :b)) 15 | (l/succeed :a 1 16 | (l/cancelled :a) 17 | (l/cancelled :b)) 18 | (l/fail :b err1 19 | (l/succeeded :main #{1}))))))) 20 | 21 | (t/deftest fail 22 | (t/is (= [] 23 | (lc/run 24 | (l/store 25 | (m/race (l/task :a) (l/task :b)) 26 | (l/start :main (l/started :a) (l/started :b)) 27 | (l/fail :a err1) 28 | (l/fail :b err2 29 | (l/failed :main #(= [err1 err2] (::m/errors (ex-data %)))))))))) 30 | 31 | (t/deftest cancel 32 | (t/is (= [] 33 | (lc/run 34 | (l/store 35 | (m/race (l/task :a) (l/task :b)) 36 | (l/start :main (l/started :a) (l/started :b)) 37 | (l/cancel :main 38 | (l/cancelled :a) 39 | (l/cancelled :b))))))) 40 | -------------------------------------------------------------------------------- /test/missionary/rdv_test.cljc: -------------------------------------------------------------------------------- 1 | (ns missionary.rdv-test 2 | (:require [lolcat.core :as lc] 3 | [lolcat.lib :as l] 4 | [missionary.core :as m] 5 | [clojure.test :as t]) 6 | (:import [missionary Cancelled])) 7 | 8 | (lc/defword give [v & events] [v (l/swap) (lc/call 1) (apply l/start :give events)]) 9 | 10 | (t/deftest success 11 | (t/testing "give then take" 12 | (t/is (= [] 13 | (lc/run 14 | (l/store 15 | (m/rdv) (l/dup) (l/-rot) ; rdv store rdv 16 | (give 1) (l/swap) ; store rdv 17 | (l/start :take 18 | (l/succeeded :give nil?) 19 | (l/succeeded :take #{1}))))))) 20 | (t/testing "take then give" 21 | (t/is (= [] 22 | (lc/run 23 | (l/store 24 | (m/rdv) (l/dup) (l/-rot) ; rdv store rdv 25 | (l/start :take) (l/swap) ; store rdv 26 | (give 1 27 | (l/succeeded :take #{1}) 28 | (l/succeeded :give nil?)))))))) 29 | 30 | (t/deftest cancel 31 | (t/testing "give" 32 | (t/is (= [] 33 | (lc/run 34 | (l/store 35 | (m/rdv) 36 | (give 1) 37 | (l/cancel :give 38 | (l/failed :give (partial instance? Cancelled)))))))) 39 | (t/testing "take" 40 | (t/is (= [] 41 | (lc/run 42 | (l/store 43 | (m/rdv) 44 | (l/start :take) 45 | (l/cancel :take 46 | (l/failed :take (partial instance? Cancelled))))))))) 47 | -------------------------------------------------------------------------------- /test/missionary/reduce_test.cljc: -------------------------------------------------------------------------------- 1 | (ns missionary.reduce-test 2 | (:require [lolcat.core :as lc] 3 | [lolcat.lib :as l] 4 | [missionary.core :as m] 5 | [clojure.test :as t])) 6 | 7 | (t/deftest success 8 | (t/is (= [] 9 | (lc/run 10 | (l/store 11 | (m/reduce conj (l/flow :input)) 12 | (l/start :main (l/spawned :input)) 13 | (l/notify :input (l/transferred :input 1)) 14 | (l/notify :input (l/transferred :input 2)) 15 | (l/terminate :input 16 | (l/succeeded :main #{[1 2]}))))))) 17 | 18 | (def err (ex-info "" {})) 19 | (t/deftest flow-throws 20 | (t/is (= [] 21 | (lc/run 22 | (l/store 23 | (m/reduce conj (l/flow :input)) 24 | (l/start :main (l/spawned :input)) 25 | (l/notify :input 26 | (l/crashed :input err) 27 | (l/cancelled :input)) 28 | (l/terminate :input 29 | (l/failed :main #{err}))))))) 30 | 31 | (t/deftest rf-throws 32 | (t/is (= [] 33 | (lc/run 34 | (l/store 35 | (m/reduce (fn [_ _] (throw err)) [] (l/flow :input)) 36 | (l/start :main (l/spawned :input)) 37 | (l/notify :input 38 | (l/transferred :input 1) 39 | (l/cancelled :input)) 40 | (l/terminate :input 41 | (l/failed :main #{err}))))))) 42 | 43 | (t/deftest rf-init-throws 44 | (t/is (= [] 45 | (lc/run 46 | (l/store 47 | (m/reduce (fn ([] (throw err)) ([a b] (conj a b))) (l/flow :input)) 48 | (l/start :main 49 | (l/spawned :input) 50 | (l/cancelled :input)) 51 | (l/terminate :input 52 | (l/failed :main #{err}))))))) 53 | 54 | (t/deftest rf-returns-reduced 55 | (t/is (= [] 56 | (lc/run 57 | (l/store 58 | (m/reduce (fn [_ _] (reduced :done)) [] (l/flow :input)) 59 | (l/start :main (l/spawned :input)) 60 | (l/notify :input 61 | (l/transferred :input 1) 62 | (l/cancelled :input)) 63 | (l/terminate :input 64 | (l/succeeded :main #{:done}))))))) 65 | 66 | (t/deftest cancel 67 | (t/testing "success" 68 | (t/is (= [] 69 | (lc/run 70 | (l/store 71 | (m/reduce conj (l/flow :input)) 72 | (l/start :main (l/spawned :input)) 73 | (l/cancel :main 74 | (l/cancelled :input)) 75 | (l/notify :input 76 | (l/transferred :input 1)) 77 | (l/terminate :input 78 | (l/succeeded :main #{[1]}))))))) 79 | (t/testing "crash" 80 | (t/is (= [] 81 | (lc/run 82 | (l/store 83 | (m/reduce conj (l/flow :input)) 84 | (l/start :main (l/spawned :input)) 85 | (l/cancel :main 86 | (l/cancelled :input)) 87 | (l/notify :input 88 | (l/crashed :input err) 89 | (l/cancelled :input)) 90 | (l/terminate :input 91 | (l/failed :main #{err})))))))) 92 | -------------------------------------------------------------------------------- /test/missionary/reductions_test.cljc: -------------------------------------------------------------------------------- 1 | (ns missionary.reductions-test 2 | (:require [lolcat.core :as lc] 3 | [lolcat.lib :as l] 4 | [missionary.core :as m] 5 | [clojure.test :as t])) 6 | 7 | (lc/defword init [flow] [flow (l/spawn :main (l/spawned :input) (l/notified :main))]) 8 | 9 | (t/deftest simple-with-cancel 10 | (t/is (= [] 11 | (lc/run 12 | (l/store 13 | (init (m/reductions + 1 (l/flow :input))) 14 | (l/transfer :main) 15 | (l/check #{1}) 16 | (l/notify :input 17 | (l/notified :main)) 18 | (l/transfer :main 19 | (l/transferred :input 2)) 20 | (l/check #{3}) 21 | (l/notify :input 22 | (l/notified :main)) 23 | (l/transfer :main 24 | (l/transferred :input 3)) 25 | (l/check #{6}) 26 | (l/cancel :main 27 | ;; reductions is in charge of input, so it cancels it 28 | (l/cancelled :input))))))) 29 | 30 | (t/deftest init-terminates-before-main-transfers 31 | (t/is (= [] 32 | (lc/run 33 | (l/store 34 | (init (m/reductions + 1 (l/flow :input))) 35 | ;; we don't terminate yet, since init wasn't transfered 36 | (l/terminate :input) 37 | (l/transfer :main ;; now we do 38 | (l/terminated :main)) 39 | (l/check #{1})))))) 40 | 41 | (t/deftest terminate 42 | (t/is (= [] 43 | (lc/run 44 | (l/store 45 | (init (m/reductions + 1 (l/flow :input))) 46 | (l/transfer :main) 47 | (l/check #{1}) 48 | (l/terminate :input 49 | (l/terminated :main))))))) 50 | 51 | (def err (ex-info "" {})) 52 | 53 | (t/deftest reducer-throws 54 | (t/is (= [] 55 | (lc/run 56 | (l/store 57 | (init (m/reductions (fn [_ _] (throw err)) 1 (l/flow :input))) 58 | (l/transfer :main) 59 | (l/check #{1}) 60 | (l/notify :input 61 | (l/notified :main)) 62 | (l/crash :main 63 | (l/transferred :input 2) 64 | (l/cancelled :input)) 65 | (l/check #{err})))))) 66 | 67 | (t/deftest crash-input 68 | (t/is (= [] 69 | (lc/run 70 | (l/store 71 | (init (m/reductions + 1 (l/flow :input))) 72 | (l/transfer :main) 73 | (l/check #{1}) 74 | (l/notify :input 75 | (l/notified :main)) 76 | (l/crash :main 77 | (l/crashed :input err) 78 | (l/cancelled :input)) 79 | (l/check #{err}))))) 80 | (t/testing "transfer init value after being notified from input" 81 | (t/is (= [] 82 | (lc/run 83 | (l/store 84 | (init (m/reductions + 1 (l/flow :input))) 85 | (l/notify :input) 86 | (l/transfer :main 87 | (l/notified :main)) 88 | (l/check #{1}) 89 | (l/crash :main 90 | (l/crashed :input err) 91 | (l/cancelled :input)) 92 | (l/check #{err}))))))) 93 | -------------------------------------------------------------------------------- /test/missionary/relieve_test.cljc: -------------------------------------------------------------------------------- 1 | (ns missionary.relieve-test 2 | (:require [lolcat.core :as lc] 3 | [lolcat.lib :as l] 4 | [missionary.core :as m] 5 | [clojure.test :as t])) 6 | 7 | (lc/defword init [flow] [flow (l/spawn :main (l/spawned :input))]) 8 | 9 | (t/deftest simple-with-cancel 10 | (t/is (= [] 11 | (lc/run 12 | (l/store 13 | (init (m/relieve + (l/flow :input))) 14 | (l/notify :input 15 | (l/transferred :input 1) 16 | (l/notified :main)) 17 | (l/transfer :main) 18 | (l/check #{1}) 19 | (l/cancel :main 20 | (l/cancelled :input))))))) 21 | 22 | (def reduced! (l/compose (l/check #{:reduced}) nil)) 23 | 24 | (t/deftest overflow 25 | (t/is (= [] 26 | (lc/run 27 | (l/store 28 | (init (m/relieve (fn [ac nx] (lc/event :reduced) (+ ac nx)) (l/flow :input))) 29 | (l/notify :input 30 | (l/transferred :input 1) 31 | (l/notified :main)) 32 | (l/notify :input 33 | (l/transferred :input 2) 34 | reduced!) 35 | (l/notify :input 36 | (l/transferred :input 3) 37 | reduced!) 38 | (l/transfer :main) 39 | (l/check #{6})))))) 40 | 41 | (t/deftest terminate 42 | (t/is (= [] 43 | (lc/run 44 | (l/store 45 | (init (m/relieve + (l/flow :input))) 46 | (l/terminate :input 47 | (l/terminated :main)))))) 48 | (t/testing "but there's still value to transfer" 49 | (t/is (= [] 50 | (lc/run 51 | (l/store 52 | (init (m/relieve + (l/flow :input))) 53 | (l/notify :input 54 | (l/transferred :input 1) 55 | (l/notified :main)) 56 | (l/terminate :input) 57 | (l/transfer :main 58 | (l/terminated :main)) 59 | (l/check #{1}))))))) 60 | 61 | (def err (ex-info "" {})) 62 | 63 | (t/deftest rf-throws 64 | (t/is (= [] 65 | (lc/run 66 | (l/store 67 | (init (m/relieve (fn [_ _] (throw err)) (l/flow :input))) 68 | (l/notify :input 69 | (l/transferred :input :doesnt-matter) 70 | (l/notified :main)) 71 | (l/notify :input 72 | (l/transferred :input 0) 73 | (l/cancelled :input)) 74 | (l/crash :main) 75 | (l/check #{err})))))) 76 | -------------------------------------------------------------------------------- /test/missionary/sample_test.cljc: -------------------------------------------------------------------------------- 1 | (ns missionary.sample-test 2 | (:require [lolcat.core :as lc] 3 | [lolcat.lib :as l] 4 | [missionary.core :as m] 5 | [clojure.test :as t])) 6 | 7 | (lc/defword init [flow] 8 | [flow 9 | (l/spawn :main 10 | (l/spawned :x (l/notify :x)) 11 | (l/spawned :y (l/notify :y)) 12 | (l/spawned :sampler)) 13 | (l/notify :sampler 14 | (l/notified :main))]) 15 | 16 | (t/deftest simple-with-cancel 17 | (t/is (= [] 18 | (lc/run 19 | (l/store 20 | (init (m/sample vector (l/flow :x) (l/flow :y) (l/flow :sampler))) 21 | (l/transfer :main 22 | (l/transferred :x :x1) 23 | (l/transferred :y :y1) 24 | (l/transferred :sampler :sampler1)) 25 | (l/check #{[:x1 :y1 :sampler1]}) 26 | (l/cancel :main 27 | (l/cancelled :x) 28 | (l/cancelled :y) 29 | (l/cancelled :sampler)) 30 | (l/terminate :sampler 31 | (l/cancelled :x) 32 | (l/cancelled :y))))))) 33 | 34 | (t/deftest consecutive-notify-causes-retransfer 35 | (t/is (= [] 36 | (lc/run 37 | (l/store 38 | (init (m/sample vector (l/flow :x) (l/flow :y) (l/flow :sampler))) 39 | (l/transfer :main 40 | (l/transferred :x (l/compose (l/notify :x) :x1)) 41 | (l/transferred :x :x2) 42 | (l/transferred :y :y1) 43 | (l/transferred :sampler :sampler1)) 44 | (l/check #{[:x2 :y1 :sampler1]}) 45 | (l/cancel :main 46 | (l/cancelled :x) 47 | (l/cancelled :y) 48 | (l/cancelled :sampler)) 49 | (l/terminate :sampler 50 | (l/cancelled :x) 51 | (l/cancelled :y))))))) 52 | 53 | (def err (ex-info "" {})) 54 | (t/deftest input-crashes 55 | (t/is (= [] 56 | (lc/run 57 | (l/store 58 | (init (m/sample vector (l/flow :x) (l/flow :y) (l/flow :sampler))) 59 | (l/crash :main 60 | (l/crashed :x err) 61 | (l/transferred :sampler :sampler1) 62 | (l/cancelled :sampler)) 63 | (l/check #{err}) 64 | (l/terminate :sampler 65 | (l/cancelled :x) 66 | (l/cancelled :y) 67 | (l/transferred :y :y1))))))) 68 | 69 | (def f-called (l/compose (l/check #{:f}) nil)) 70 | 71 | (t/deftest f-crashes 72 | (t/is (= [] 73 | (lc/run 74 | (l/store 75 | (init (m/sample (fn [& _] (lc/event :f) (throw err)) (l/flow :x) (l/flow :y) (l/flow :sampler))) 76 | (l/crash :main 77 | (l/transferred :x :x1) 78 | (l/transferred :y :y1) 79 | (l/transferred :sampler :sampler1) 80 | f-called 81 | (l/cancelled :sampler)) 82 | (l/check #{err}) 83 | (l/terminate :sampler 84 | (l/cancelled :x) 85 | (l/cancelled :y))))))) 86 | 87 | (t/deftest input-terminates 88 | (t/is (= [] 89 | (lc/run 90 | (l/store 91 | (init (m/sample vector (l/flow :x) (l/flow :y) (l/flow :sampler))) 92 | (l/transfer :main 93 | (l/transferred :x :x1) 94 | (l/transferred :y :y1) 95 | (l/transferred :sampler :sampler1)) 96 | (l/check #{[:x1 :y1 :sampler1]}) 97 | (l/terminate :x) 98 | (l/terminate :y) 99 | (l/terminate :sampler 100 | (l/cancelled :x) 101 | (l/cancelled :y) 102 | (l/terminated :main))))))) 103 | -------------------------------------------------------------------------------- /test/missionary/seed_test.cljc: -------------------------------------------------------------------------------- 1 | (ns missionary.seed-test 2 | (:require [lolcat.core :as lc] 3 | [lolcat.lib :as l] 4 | [missionary.core :as m] 5 | [clojure.test :as t]) 6 | (:import [missionary Cancelled])) 7 | 8 | (t/deftest seed 9 | (t/is (= [] 10 | (lc/run 11 | (l/store 12 | (m/seed [1 2 3]) 13 | (l/spawn :main 14 | (l/notified :main)) 15 | (l/transfer :main 16 | (l/notified :main)) 17 | (l/check #{1}) 18 | (l/cancel :main) 19 | (l/crash :main 20 | (l/terminated :main)) 21 | (l/check #(instance? Cancelled %))))))) 22 | -------------------------------------------------------------------------------- /test/missionary/sem_test.cljc: -------------------------------------------------------------------------------- 1 | (ns missionary.sem-test 2 | (:require [lolcat.core :as lc] 3 | [lolcat.lib :as l] 4 | [missionary.core :as m] 5 | [clojure.test :as t]) 6 | (:import [missionary Cancelled])) 7 | 8 | (lc/defword release [id & events] [(l/dup) (l/change get id) (apply lc/call 0 events) (l/lose)]) 9 | (lc/defword acquire [id lock-id & events] [(l/dup) (l/change get id) (apply l/start lock-id events)]) 10 | (defn acquired [lock-id] (l/succeeded lock-id nil?)) 11 | 12 | (t/deftest simple 13 | (t/is (= [] 14 | (lc/run 15 | (l/store 16 | (m/sem) (l/insert :sem) 17 | (acquire :sem :lock1 (acquired :lock1)) 18 | (acquire :sem :lock2) 19 | (release :sem (acquired :lock2))))))) 20 | 21 | (t/deftest more-tokens 22 | (t/is (= [] 23 | (lc/run 24 | (l/store 25 | (m/sem 3) (l/insert :sem) 26 | (acquire :sem :lock1 (acquired :lock1)) 27 | (acquire :sem :lock2 (acquired :lock2)) 28 | (acquire :sem :lock3 (acquired :lock3)) 29 | (acquire :sem :lock4) 30 | (release :sem (acquired :lock4))))))) 31 | 32 | (t/deftest cancel 33 | (t/is (= [] 34 | (lc/run 35 | (l/store 36 | (m/sem) (l/insert :sem) 37 | (acquire :sem :lock1 (acquired :lock1)) 38 | (acquire :sem :lock2) 39 | (l/cancel :lock2 (l/failed :lock2 (partial instance? Cancelled)))))))) 40 | 41 | (defn sem 42 | ([] (lc/event :released)) 43 | ([s _f] (s (lc/event :acquired)))) 44 | 45 | (t/deftest holding 46 | (t/is (= [] 47 | (lc/run 48 | (l/store 49 | (m/sp (m/holding sem (lc/event :return-event))) 50 | (l/start :main 51 | (l/compose (l/check #{:acquired}) nil) 52 | (l/compose (l/check #{:return-event}) :val) 53 | (l/compose (l/check #{:released}) nil) 54 | (l/succeeded :main #{:val}))))))) 55 | -------------------------------------------------------------------------------- /test/missionary/sp_test.cljc: -------------------------------------------------------------------------------- 1 | (ns missionary.sp-test 2 | (:require [lolcat.core :as lc] 3 | [lolcat.lib :as l] 4 | [missionary.core :as m] 5 | [clojure.test :as t])) 6 | 7 | (t/deftest success 8 | (t/is (= [] 9 | (lc/run 10 | (l/store 11 | (m/sp 12 | (l/detect :first 1) 13 | (let [v (m/? (l/task :input))] 14 | (l/detect :second 2) 15 | v)) 16 | (l/start :main 17 | (l/detected :first) 18 | (l/started :input)) 19 | (l/succeed :input 2 20 | (l/detected :second) 21 | (l/succeeded :main #{2}))))))) 22 | 23 | (def err (ex-info "" {})) 24 | (t/deftest failure 25 | (t/is (= [] 26 | (lc/run 27 | (l/store 28 | (m/sp 29 | (l/detect :first 1) 30 | (let [v (m/? (l/task :input))] 31 | (l/detect :second 2) 32 | v)) 33 | (l/start :main 34 | (l/detected :first) 35 | (l/started :input)) 36 | (l/fail :input err 37 | (l/failed :main #{err}))))))) 38 | -------------------------------------------------------------------------------- /test/missionary/store_test.cljc: -------------------------------------------------------------------------------- 1 | (ns missionary.store-test 2 | (:require [lolcat.core :as lc] 3 | [lolcat.lib :as l] 4 | [missionary.core :as m] 5 | [clojure.test :as t]) 6 | (:import missionary.Cancelled)) 7 | 8 | (lc/defword append [s x & events] 9 | [x s 10 | (apply lc/call 1 events) 11 | (lc/drop 0)]) 12 | 13 | (lc/defword freeze [s & events] 14 | [s 15 | (apply lc/call 0 events) 16 | (lc/drop 0)]) 17 | 18 | (t/deftest append-read 19 | (t/is 20 | (= [] 21 | (let [!x (m/store + 0)] 22 | (lc/run 23 | (l/store 24 | (append !x 2) 25 | (append !x 3) 26 | !x (l/spawn :r1 (l/notified :r1)) 27 | (l/transfer :r1) 28 | (l/check #{5}) 29 | (append !x 1 (l/notified :r1)) 30 | (append !x 2) 31 | (l/transfer :r1) 32 | (l/check #{3}) 33 | (l/cancel :r1 (l/notified :r1)) 34 | (append !x 4) 35 | (l/crash :r1 (l/terminated :r1)) 36 | (l/check #(instance? Cancelled %)) 37 | !x (l/spawn :r2 (l/notified :r2)) 38 | (append !x 5) 39 | (l/transfer :r2) 40 | (l/check #{17}))))))) 41 | 42 | (t/deftest freeze-before-read 43 | (t/is 44 | (= [] 45 | (let [!x (m/store + 0)] 46 | (lc/run 47 | (l/store 48 | (freeze !x) 49 | !x (l/spawn :r1 (l/notified :r1)) 50 | (l/transfer :r1 (l/terminated :r1)) 51 | (l/check #{0}))))))) 52 | 53 | (t/deftest freeze-after-read 54 | (t/is 55 | (= [] 56 | (let [!x (m/store + 0)] 57 | (lc/run 58 | (l/store 59 | !x (l/spawn :r1 (l/notified :r1)) 60 | (l/transfer :r1) 61 | (l/check #{0}) 62 | (freeze !x (l/terminated :r1)) 63 | !x (l/spawn :r2 (l/notified :r2)) 64 | (l/transfer :r2 (l/terminated :r2)) 65 | (l/check #{0}))))))) 66 | 67 | (t/deftest freeze-during-transfer 68 | (t/is 69 | (= [] 70 | (let [!x (m/store + 0)] 71 | (lc/run 72 | (l/store 73 | !x (l/spawn :r1 (l/notified :r1)) 74 | (freeze !x) 75 | (l/transfer :r1 (l/terminated :r1)) 76 | (l/check #{0}))))))) 77 | 78 | (t/deftest crash-during-tranfer 79 | (t/is 80 | (= [] 81 | (let [!x (m/store (fn [_ _] (throw (ex-info "boom" {:flag true}))) nil)] 82 | (lc/run 83 | (l/store 84 | !x (l/spawn :r1 (l/notified :r1)) 85 | (append !x nil) 86 | (append !x nil) 87 | (l/crash :r1 (l/terminated :r1)) 88 | (l/check #(:flag (ex-data %))) 89 | !x (l/spawn :r2 (l/notified :r2)) 90 | (l/crash :r2 (l/terminated :r2)) 91 | (l/check #(:flag (ex-data %))))))))) -------------------------------------------------------------------------------- /test/missionary/sub_test.clj: -------------------------------------------------------------------------------- 1 | (ns missionary.sub-test 2 | (:require [lolcat.core :as lc] 3 | [lolcat.lib :as l] 4 | [missionary.core :as m] 5 | [clojure.test :as t]) 6 | (:import (missionary Cancelled) 7 | (org.reactivestreams Publisher Subscriber Subscription))) 8 | 9 | (defn publisher [id] 10 | (reify Publisher 11 | (subscribe [_ s] 12 | (lc/event [:subscribed id s])))) 13 | 14 | (defn subscription [id] 15 | (reify Subscription 16 | (request [_ n] (lc/event [:requested id n])) 17 | (cancel [_] (lc/event [:cancelled id])))) 18 | 19 | (defn subscribe! [^Subscriber subscriber ^Subscription subscription] 20 | (.onSubscribe subscriber subscription)) 21 | 22 | (defn complete! [^Subscriber subscriber] 23 | (.onComplete subscriber)) 24 | 25 | (defn error! [^Subscriber subscriber ^Throwable error] 26 | (.onError subscriber error)) 27 | 28 | (defn next! [^Subscriber subscriber value] 29 | (.onNext subscriber value)) 30 | 31 | (lc/defword subscribed [id] 32 | [(l/bi peek pop) 33 | (l/check #{[:subscribed id]}) 34 | (l/insert id) 35 | nil]) 36 | 37 | (lc/defword requested [id n] 38 | [(l/check #{[:requested id n]}) nil]) 39 | 40 | (lc/defword cancelled [id] 41 | [(l/check #{[:cancelled id]}) nil]) 42 | 43 | (lc/defword on-subscribe [id & events] 44 | [(lc/copy 0) 45 | (l/change get id) 46 | (subscription id) 47 | subscribe! 48 | (apply lc/call 2 events) 49 | (lc/drop 0)]) 50 | 51 | (lc/defword on-next [id & events] 52 | [(lc/copy 1) 53 | (l/change get id) 54 | (l/swap) 55 | next! 56 | (apply lc/call 2 events) 57 | (lc/drop 0)]) 58 | 59 | (lc/defword on-error [id & events] 60 | [(lc/copy 1) 61 | (l/change get id) 62 | (l/swap) 63 | error! 64 | (apply lc/call 2 events) 65 | (lc/drop 0)]) 66 | 67 | (lc/defword on-complete [id & events] 68 | [(lc/copy 0) 69 | (l/change get id) 70 | complete! 71 | (apply lc/call 1 events) 72 | (lc/drop 0)]) 73 | 74 | (t/deftest cancel-initializing 75 | (t/is (= [] 76 | (lc/run 77 | (l/store 78 | (m/subscribe (publisher :input)) 79 | (l/spawn :main 80 | (subscribed :input)) 81 | (l/cancel :main 82 | (l/notified :main)) 83 | (l/crash :main 84 | (l/terminated :main)) 85 | (l/check (partial instance? Cancelled)) 86 | (on-subscribe :input 87 | (cancelled :input))))))) 88 | 89 | (t/deftest cancel-waiting 90 | (t/is (= [] 91 | (lc/run 92 | (l/store 93 | (m/subscribe (publisher :input)) 94 | (l/spawn :main 95 | (subscribed :input)) 96 | (on-subscribe :input 97 | (requested :input 1)) 98 | (l/cancel :main 99 | (cancelled :input) 100 | (l/notified :main)) 101 | :ignored (on-next :input) 102 | (l/crash :main 103 | (l/terminated :main)) 104 | (l/check (partial instance? Cancelled))))))) 105 | 106 | (t/deftest cancel-ready 107 | (t/is (= [] 108 | (lc/run 109 | (l/store 110 | (m/subscribe (publisher :input)) 111 | (l/spawn :main 112 | (subscribed :input)) 113 | (on-subscribe :input 114 | (requested :input 1)) 115 | :foo (on-next :input 116 | (l/notified :main)) 117 | (l/cancel :main 118 | (cancelled :input)) 119 | (l/transfer :main 120 | (l/notified :main)) 121 | (l/check #{:foo}) 122 | (l/crash :main 123 | (l/terminated :main)) 124 | (l/check (partial instance? Cancelled))))))) 125 | 126 | (t/deftest cancel-done 127 | (t/is (= [] 128 | (lc/run 129 | (l/store 130 | (m/subscribe (publisher :input)) 131 | (l/spawn :main 132 | (subscribed :input)) 133 | (on-subscribe :input 134 | (requested :input 1)) 135 | (on-complete :input 136 | (l/terminated :main)) 137 | (l/cancel :main)))))) 138 | 139 | (def err (Error.)) 140 | 141 | (t/deftest failure-waiting 142 | (t/is (= [] 143 | (lc/run 144 | (l/store 145 | (m/subscribe (publisher :input)) 146 | (l/spawn :main 147 | (subscribed :input)) 148 | (on-subscribe :input 149 | (requested :input 1)) 150 | err (on-error :input 151 | (l/notified :main)) 152 | (l/crash :main 153 | (l/terminated :main)) 154 | (l/check #{err})))))) 155 | 156 | (t/deftest failure-ready 157 | (t/is (= [] 158 | (lc/run 159 | (l/store 160 | (m/subscribe (publisher :input)) 161 | (l/spawn :main 162 | (subscribed :input)) 163 | (on-subscribe :input 164 | (requested :input 1)) 165 | :foo (on-next :input 166 | (l/notified :main)) 167 | err (on-error :input) 168 | (l/transfer :main 169 | (l/notified :main)) 170 | (l/check #{:foo}) 171 | (l/crash :main 172 | (l/terminated :main)) 173 | (l/check #{err})))))) -------------------------------------------------------------------------------- /test/missionary/test_dev.clj: -------------------------------------------------------------------------------- 1 | (ns missionary.test-dev 2 | (:require [kaocha runner stacktrace])) 3 | 4 | (defn run [_opts] 5 | (binding [kaocha.stacktrace/*stacktrace-filters* (conj kaocha.stacktrace/*stacktrace-filters* "kaocha.")] 6 | (kaocha.runner/exec-fn {:kaocha/watch? true, :kaocha/fail-fast? true}))) 7 | -------------------------------------------------------------------------------- /test/missionary/watch_test.cljc: -------------------------------------------------------------------------------- 1 | (ns missionary.watch-test 2 | (:require [lolcat.core :as lc] 3 | [lolcat.lib :as l] 4 | [missionary.core :as m] 5 | [clojure.test :as t]) 6 | (:import [missionary Cancelled])) 7 | 8 | (lc/defword init [flow] [flow (l/spawn :main (l/notified :main))]) 9 | 10 | (t/deftest watch 11 | (let [!a (atom 0)] 12 | (t/is (= [] 13 | (lc/run 14 | (l/store 15 | (init (m/watch !a)) 16 | (l/transfer :main) 17 | (l/check #{0}) 18 | #(swap! !a inc) 19 | (lc/call 0 20 | (l/notified :main)) 21 | (lc/drop 0) 22 | (l/transfer :main) 23 | (l/check #{1}) 24 | (l/cancel :main 25 | (l/notified :main)) 26 | (l/crash :main 27 | (l/terminated :main)) 28 | (l/check #(instance? Cancelled %)))))))) 29 | 30 | (t/deftest cancel-before-transfer 31 | (let [!a (atom 0)] 32 | (t/is (= [] 33 | (lc/run 34 | (l/store 35 | (init (m/watch !a)) 36 | (l/cancel :main) 37 | (l/crash :main 38 | (l/terminated :main)) 39 | (l/check #(instance? Cancelled %)))))))) 40 | -------------------------------------------------------------------------------- /test/missionary/zip_test.cljc: -------------------------------------------------------------------------------- 1 | (ns missionary.zip-test 2 | (:require [lolcat.core :as lc] 3 | [lolcat.lib :as l] 4 | [missionary.core :as m] 5 | [clojure.test :as t])) 6 | 7 | (lc/defword init [flow] [flow (l/spawn :main (l/spawned :x) (l/spawned :y))]) 8 | 9 | (t/deftest simple-with-cancel 10 | (t/is (= [] 11 | (lc/run 12 | (l/store 13 | (init (m/zip vector (l/flow :x) (l/flow :y))) 14 | (l/notify :x) 15 | (l/notify :y 16 | (l/notified :main)) 17 | (l/transfer :main 18 | (l/transferred :x :x1) 19 | (l/transferred :y :y1)) 20 | (l/check #{[:x1 :y1]}) 21 | (l/cancel :main 22 | (l/cancelled :x) 23 | (l/cancelled :y))))))) 24 | 25 | (t/deftest an-input-terminates 26 | (t/is (= [] 27 | (lc/run 28 | (l/store 29 | (init (m/zip vector (l/flow :x) (l/flow :y))) 30 | (l/terminate :x 31 | (l/cancelled :y)) 32 | (l/terminate :y 33 | (l/terminated :main))))))) 34 | 35 | (def err (ex-info "" {})) 36 | (def f-called (l/compose (l/check #{:f}) nil)) 37 | 38 | (t/deftest f-throws 39 | (t/is (= [] 40 | (lc/run 41 | (l/store 42 | (init (m/zip (fn [_ _] (lc/event :f) (throw err)) (l/flow :x) (l/flow :y))) 43 | (l/notify :x) 44 | (l/notify :y 45 | (l/notified :main)) 46 | (l/crash :main 47 | (l/transferred :x :x1) 48 | (l/transferred :y :y1) 49 | f-called 50 | (l/cancelled :x 51 | (l/terminate :x 52 | (l/cancelled :y 53 | (l/terminate :y 54 | (l/terminated :main)))))) 55 | (l/check #{err})))))) 56 | 57 | (t/deftest doesnt-overconsume 58 | (t/is (= [] 59 | (lc/run 60 | (l/store 61 | (init (m/zip vector (l/flow :x) (l/flow :y))) 62 | (l/notify :x) 63 | (l/notify :y 64 | (l/notified :main)) 65 | (l/transfer :main 66 | (l/transferred :x (l/terminate :x) :x1) 67 | (l/transferred :y (l/notify :y) :y1) 68 | (l/cancelled :y) 69 | (l/transferred :y (l/terminate :y) :y2) 70 | (l/terminated :main)) 71 | (l/check #{[:x1 :y1]})))))) 72 | 73 | (t/deftest empty-input 74 | (t/is (= [] 75 | (lc/run 76 | (l/store 77 | (m/zip vector (l/flow :input)) 78 | (l/spawn :main 79 | (l/spawned :input (l/terminate :input)) 80 | (l/terminated :main))))))) --------------------------------------------------------------------------------