{"id":16838877,"url":"https://github.com/akarnokd/loom-interop-experiments","last_synced_at":"2025-10-27T22:08:25.159Z","repository":{"id":148636529,"uuid":"199452081","full_name":"akarnokd/loom-interop-experiments","owner":"akarnokd","description":"Code to experiment with Project Loom continuation/fiber API.","archived":false,"fork":false,"pushed_at":"2020-02-04T09:21:21.000Z","size":68,"stargazers_count":3,"open_issues_count":0,"forks_count":1,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-01-24T11:11:19.668Z","etag":null,"topics":["continuations","fiber","fiberscope","flow","loom","reactive","reactive-streams"],"latest_commit_sha":null,"homepage":null,"language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/akarnokd.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2019-07-29T12:50:50.000Z","updated_at":"2020-02-04T09:21:23.000Z","dependencies_parsed_at":"2023-05-20T18:33:22.229Z","dependency_job_id":null,"html_url":"https://github.com/akarnokd/loom-interop-experiments","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/akarnokd%2Floom-interop-experiments","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/akarnokd%2Floom-interop-experiments/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/akarnokd%2Floom-interop-experiments/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/akarnokd%2Floom-interop-experiments/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/akarnokd","download_url":"https://codeload.github.com/akarnokd/loom-interop-experiments/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":244153308,"owners_count":20406995,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["continuations","fiber","fiberscope","flow","loom","reactive","reactive-streams"],"created_at":"2024-10-13T12:27:01.763Z","updated_at":"2025-10-27T22:08:20.122Z","avatar_url":"https://github.com/akarnokd.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"# loom-interop-experiments\nCode to experiment with Project Loom continuation/fiber API.\n\n# Features\n\nUnless mentioned otherwise, all components use the Java `Flow` Reactive classes.\n\n## Table of contents\n\n- [ContinuationPublisher](#continuationpublisher)\n- [FiberPublisher](#fiberpublisher) \u0026 [FiberPublisherScoped](#fiberpublisherscoped)\n- [FiberSubscribeOnPublisher](#fibersubscribeonpublisher)\n- [FiberMap](#fibermap)\n- [FiberConsumer](#fiberconsumer)\n- [ExecutorPool](#executorpool) \u0026 [ExecutorWorker](#executorworker)\n- [ResumableFiber](#resumablefiber)\n\n## Components\n\n### ContinuationPublisher\n\nUses the `Continuation` and `ContinuationScope` API to generate values and suspend on backpressure.\n\n```java\nvar source = new ContinuationPublisher\u003cInteger\u003e(emitter -\u003e {\n    for (int i = 0; i \u003c 10; i++) {\n        emitter.accept(i);\n    }\n});\n\nsource.subscribe( ... );\n```\n\nInternally, suspension is triggered via `Continuation.yield()` when the requested amount is zero. The resumption is triggered via\n`Continuation.run()` when the requested amount increases from zero to N. The sequence terminates when the lambda returns or throws.\n\n### FiberPublisherScoped\n\nUses the FiberScope API to create a scope for each incoming `Subscriber` to generate the items via callback.\n\n```java\nvar source = new FiberPublisher\u003cInteger\u003e((scope, emitter) -\u003e {\n    for (int i = 0; i \u003c 10; i++) {\n        var j = i;\n        emitter.accept(\n           scope.schedule(() -\u003e j).join()\n        );\n     }\n});\n\nsource.subscribe( ... );\n```\n\nThe body of the lambda is executed for each incoming `Subscriber` on a fresh `FiberScope`, also provided to the lambda body.\nInternally, suspension is based on awaiting lock condition (ReentrantLock.newCondition) when the requested amount is zero.\nThe resumption is triggered via a signal-all on the same lock condition when the requested amount increases from zero to N.\nThis type of logical blocking should only block the fiber, not any OS thread. The sequence terminates after the lambda returns or throws\nand the scope is closed.\n\nYou can fork off computation via `scope.schedule` and `join` them back. Note however that calling `emitter.accept` from inside these scheduled\ntasks is prohibited and may lead to undefined behavior due to races.\n\n### FiberPublisher\n\nUses locks to to suspend the subscribing context when there is a lack of downstream requests.\n\n```java\nvar source = new FiberPublisher\u003cInteger\u003e(emitter -\u003e {\n    try (var scope = FiberScope.open()) {\n        for (int i = 0; i \u003c 10; i++) {\n            var j = i;\n            emitter.emit(\n               scope.schedule(() -\u003e j).join()\n            );\n         }\n     }\n});\n\nsource.subscribe( ... );\n```\n\nIf the `FiberPublisher` is not subscribed to in a fiber, it will block the caller thread. Use [FiberSubscribeOnPublisher](#fibersubscribeonpublisher) to\nchange the subscription to a fiber running on a specific backing Executor.\n\n### FiberSubscribeOnPublisher\n\nSubscribes to the upstream source while running inside a Fiber backed by a per-subscriber `Executor`.\n\n```java\ntry (var pool = new ParrallelExecutorPool()) {\n    var async = new FiberSubscribeOnPublisher\u003cInteger\u003e(source, pool);\n\n    async.subscribe( ... );\n    \n    // await finishing of the subscriber\n}\n```\n\nSee [ExecutorPool](#executorpool) and [ExecutorWorker](#executorworker) about what and why the indirection around a plain `Executor` is needed\n\n### FiberMap\n\nAllows transforming each upstream item into zero or more values while running the transformation in a Fiber, thus allowing suspending code to run\nas well as itself suspending on downstream backpressure. In order to support emitting any number of result items, the callback has to talk to an\n`Emitter.emit()` call.\n\n```java\ntry (var pool = new ParrallelExecutorPool()) {\n    new FiberMap\u003cInteger, String\u003e(source, (value, emitter) -\u003e {\n        emitter.emit(\"\" + value);\n        emitter.emit(\"\" + (value + 1));\n    }, pool, 16);\n}\n```\n\nThe custom `FiberMapper` and `Emitter` interfaces are necessary because the underlying suspension mechanism, and in general, `Fiber.join()` can throw and would\nmake a `Consumer\u003cT\u003e` and standard functional interfaces work around not being able to throw checked exceptions.\nSince mapping can take some arbitrary time, the `prefetch` parameter allows the upstream to generate some values while the mapper block is still working,\nwhich improves the throughput of the setup.\n\n### FiberConsumer\n\nRuns a `Publisher` and through a returned `Iterator`, every next source items are made available upon each `next()` call in a fiber-blocking fashion.\n\n```java\ntry (FiberScope scope = FiberScope.open()) {\n\n    var sp = new SubmissionPublisher\u003cInteger\u003e();\n    \n    try (var iter = new FiberConsumer\u003c\u003e(sp).iterator()) {\n        scope.schedule(() -\u003e {\n            for (int i = 1; i \u003c 10; i++) {\n                 sp.submit(i);\n             }\n             sp.close();\n        });\n        \n        while (iter.hasNext()) {\n            System.out.println(iter.next());\n        }\n    }\n}\n```\n\nUnfortunately, the standard for-each over `Iterable` doesn't work because when the control would leave the iteration, the upstream subscription should be cancelled. Therefore, a custom `CloseableIterator` is returned to be used with the **try-with-resources** construct. \n\n### ExecutorPool\n\nFibers can be executed on any `Executor` and usually it is the `ForkJoinPool.commonPool()`. However, sometimes the number of carrier threads could be limited\nto a handful of threads and perhaps each subscriber would like to run on a known and/or dedicated thread/fiber context.\n\nTherefore, instead of using an `Executor` parameter in the `FiberXYZPublisher` operators, a two-step structure is employed, similar to how `Iterable`/`Iterator` is split.\n\nAn `ExecutorPool` is an auto-closeable resource which has one method, `worker()` to get an [`ExecutorWorker`](#executorworker) instance.\nOperators will ask for this worker and have to release it eventually.\n\nCurrently, some pool implementations are available:\n\n- `SingleExecutorPool` backed by a single-threaded standard executor service.\n- `ParallelExecutorPool` backed by a number of single-threaded standard executor services, which are handed out on a round-robin fashion.\n- `ForkJoinExecutorPool` backed by the `ForkJoinPool.commonPool`.\n\n```java\ntry (var pool = new SingleExecutorPool()) {\n    try (var worker = pool.worker()) {\n        try (var scope = FiberScope.open()) {\n        \n             var v = scope.schedule(worker, () -\u003e 1).join()\n             \n             System.out.println(v);\n        }\n    }\n}\n```\n\n### ExecutorWorker\n\nRepresents a concrete `Executor` resource to be used with `FiberScope.schedule` calls for example. To support dynamic scaling of certain possible pool implementations, `ExecutorWorker` is auto-closeable and should be closed once there is no further need for it.\n\n```java\nclass SomeOperation {\n    final ExecutorWorker worker;\n    \n    SomeOperation(ExecutorWorker worker) {\n         this.worker = worker;\n    }\n    \n    void someMethod() {\n    }\n    \n    void anotherMethod() {\n    }\n    \n    void finalMethod() {\n        worker.close();\n     }\n}\n```\n\n### ResumableFiber\n\nA basic continuation primitive that can suspend and resume a Fiber (or Thread), usable for pausing on backpressure or data not available yet.\n\nThe most basic use is a ping-pong between fibers.\n\n```java\nvar producerReady = new ResumableFiber();\nvar queue = new ConcurrentQueue\u003cInteger\u003e();\nvar done = new AtomicBoolean();\n\ntry (var scope = FiberScope.open()) {\n\n    var job = scope.schedule(() -\u003e {\n        for (int i = 0; i \u003c 1000; i++) {\n             queue.offer(i);\n             producerReady.resume();\n        }\n        done.set(true);\n        producerReady.resume();\n    ));\n\n    while (!done.get()) {\n        var v = queue.poll();\n        if (v != null) {\n            System.out.println(\"Got \" + v);\n            continue;\n        }\n        \n        producerReady.await();\n    }\n\n    job.join();\n}\n```\n\nIn this example, the main fiber is polling on the shared queue and only suspending if it appears to be empty. The producer side, however,\nhas to indicate resume() after every offer in case the consumer is/was suspended. Calling `resume()` from multiple threads and multiple times\nis allowed and won't by itself trigger multiple resumptions. \n\nHowever, similar to the queue-drain approach, state has to be prepared and properly released before calling `resume()` by using the appropriate\nmemory fences. In the example, `offer` does this.","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fakarnokd%2Floom-interop-experiments","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fakarnokd%2Floom-interop-experiments","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fakarnokd%2Floom-interop-experiments/lists"}