{"id":15069145,"url":"https://github.com/rcardin/sus4s","last_synced_at":"2025-07-04T22:35:07.163Z","repository":{"id":239651192,"uuid":"800152335","full_name":"rcardin/sus4s","owner":"rcardin","description":"A Direct-Style Scala Wrapper Around the Structured Concurrency of Project Loom","archived":false,"fork":false,"pushed_at":"2025-06-30T13:36:49.000Z","size":62,"stargazers_count":28,"open_issues_count":13,"forks_count":1,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-06-30T14:41:53.284Z","etag":null,"topics":["concurrent-programming","direct-style","jvm","loom","scala3","structured-concurrency"],"latest_commit_sha":null,"homepage":"","language":"Scala","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/rcardin.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,"zenodo":null}},"created_at":"2024-05-13T19:55:06.000Z","updated_at":"2025-05-03T12:45:30.000Z","dependencies_parsed_at":"2024-05-13T21:25:52.834Z","dependency_job_id":"7fdd5d89-e200-4ad8-866e-ff50fd6c6213","html_url":"https://github.com/rcardin/sus4s","commit_stats":null,"previous_names":["rcardin/sus4s"],"tags_count":3,"template":false,"template_full_name":null,"purl":"pkg:github/rcardin/sus4s","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rcardin%2Fsus4s","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rcardin%2Fsus4s/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rcardin%2Fsus4s/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rcardin%2Fsus4s/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/rcardin","download_url":"https://codeload.github.com/rcardin/sus4s/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rcardin%2Fsus4s/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":263628252,"owners_count":23490938,"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":["concurrent-programming","direct-style","jvm","loom","scala3","structured-concurrency"],"created_at":"2024-09-25T01:40:42.360Z","updated_at":"2025-07-04T22:35:07.104Z","avatar_url":"https://github.com/rcardin.png","language":"Scala","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cdiv align=\"center\"\u003e\n  \u003cpicture\u003e\n    \u003csource media=\"(prefers-color-scheme: dark)\" srcset=\"https://github.com/rcardin/sus4s/assets/16861531/ff6ff1fb-fb9f-45fc-a933-59d0036d4a60\"\u003e\n    \u003csource media=\"(prefers-color-scheme: light)\" srcset=\"https://github.com/rcardin/sus4s/assets/16861531/be98576b-e5c3-443b-9b67-cf291ecd7b79\"\u003e\n    \u003cimg alt=\"sus4s: A Direct-Style Scala Wrapper around the Structured Concurrency of Project Loom\"\n         src=\"https://github.com/rcardin/sus4s/assets/16861531/ff6ff1fb-fb9f-45fc-a933-59d0036d4a60\"\n         width=\"20%\"\u003e\n  \u003c/picture\u003e\n  \u003cbr/\u003e\u003cbr/\u003e\n  \u003cdiv\u003e\n    \u003cimg src=\"https://img.shields.io/github/actions/workflow/status/rcardin/sus4s/scala.yml?branch=main\" alt=\"GitHub Workflow Status (with branch)\"\u003e\n    \u003cimg src=\"https://img.shields.io/maven-central/v/in.rcard.sus4s/core_3\" alt=\"Maven Central\"\u003e\n    \u003cimg src=\"https://img.shields.io/github/v/release/rcardin/sus4s\" alt=\"GitHub release (latest by date)\"\u003e\n    \u003ca href=\"https://javadoc.io/doc/in.rcard.sus4s/core_3\"\u003e\n      \u003cimg src=\"https://javadoc.io/badge2/in.rcard.sus4s/core_3/javadoc.svg\" alt=\"javadoc\"\u003e\n    \u003c/a\u003e\n  \u003c/div\u003e\n\u003c/div\u003e  \n\n# sus4s\n\nA Direct-Style Scala Wrapper Around the Structured Concurrency of Project Loom\n\n## Dependency\n\nThe library is available on Maven Central. To use it, add the following dependency to your `build.sbt` files:\n\n```sbt\nlibraryDependencies += \"in.rcard.sus4s\" %% \"core\" % \"0.0.3\"\n```\n\nThe library is only available for Scala 3.\n\n## Usage\n\nThe library provides a direct-style API for Project Loom's structured concurrency. It requires JDK 21 and Scala 3. Moreover, Java preview features must be enabled in the Scala compiler.\n\nThe main entry point is the `sus4s` package object. The following code snippet shows how to use the library:\n\n```scala 3\nimport in.rcard.sus4s.sus4s.*\nimport scala.concurrent.duration.*\n\nval result: Int = structured {\n  val job1: Job[Int] = fork {\n    delay(1.second)\n    42\n  }\n  val job2: Job[Int] = fork {\n    delay(500.millis)\n    43\n  }\n  job1.value + job2.value\n}\nprintln(result) // 85\n```\n\nThe `structured` method creates a new structured concurrency scope represented by the `Suspend` trait. It's built on the `java.util.concurrent.StructuredTaskScope` class. Hence, the threads forked inside the `structured` block are Java Virtual Threads.\n\nThe `fork` method creates a new Java Virtual Thread that executes the given code block. The `fork` method executes functions declared with the capability of suspend:\n\n```scala 3\ndef findUserById(id: UserId): Suspend ?=\u003e User\n```\n\nColoring a function with the `Suspend` capability tells the caller that the function performs a suspendable operation, aka some effect. Suspension is managed by the Loom runtime, which is responsible for scheduling the virtual threads.\n\nA type alias is available for the `Suspend` capability:\n\n```scala 3\ntype Suspended[A] = Suspend ?=\u003e A\n```\n\nSo, the above function can be rewritten as:\n\n```scala 3\ndef findUserById(id: UserId): Suspended[User]\n```\n\nThe `structured` function uses structured concurrency to run the suspendable tasks. In detail, it ensures that the thread executing the block waits to complete all the forked tasks. The structured blocks terminate when:\n\n- all the forked tasks complete successfully\n- one of the forked tasks throws an exception\n- the block throws an exception\n\n## The `Job` Class\n\nForking a suspendable function means creating a new virtual thread that executes the function. The thread is represented by the `Job` class. The `Job` class provides the `value` method that waits for the completion of the virtual thread and returns the result of the function:\n\n```scala 3\nval job1: Job[Int] = fork {\n  delay(1.second)\n  42\n}\nval meaningOfLife: Int = job1.value\n```\n\nIf you're not interested in the result of the function, you can use the `join` method:\n\n```scala 3\nval job1: Job[Int] = fork {\n  delay(1.second)\n  println(\"The meaning of life is 42\")\n}\njob1.join()\n```\n\nThe `structured` function is entirely transparent to any exception thrown by the block or forked tasks.\n\n## Canceling a Job\n\nCanceling a job is possible by calling the `cancel` method on the `Job` instance. The following code snippet shows how:\n\n```scala 3\nval queue = new ConcurrentLinkedQueue[String]()\nval result = structured {\n  val job1 = fork {\n    val innerCancellableJob = fork {\n      while (true) {\n        delay(2.seconds)\n        queue.add(\"cancellable\")\n      }\n    }\n    delay(1.second)\n    innerCancellableJob.cancel()\n    queue.add(\"job1\")\n  }\n  val job = fork {\n    delay(500.millis)\n    queue.add(\"job2\")\n    43\n  }\n  job.value\n}\nqueue.toArray should contain theSameElementsInOrderAs List(\"job2\", \"job1\")\nresult shouldBe 43\n```\n\nCancellation is collaborative. In the above example, the job `innerCancellableJob` is marked for cancellation by the call `innerCancellableJob.cancel()`. However, the job is not immediately canceled. The job is canceled when it reaches the first point operation that can be _interrupted_ by the JVM. Hence, cancellation is based on the concept of interruption. In the above example, the `innerCancellableJob` is canceled when it reaches the `delay(2.seconds)` operation. The job will never be canceled if we remove the `delay` operation. A similar behavior is implemented by Kotlin coroutines (see [Kotlin Coroutines - A Comprehensive Introduction / Cancellation](https://blog.rockthejvm.com/kotlin-coroutines-101/#7-cancellation) for further details).\n\nCancelling a job follows the relationship between parent and child jobs. If a parent's job is canceled, all the children's jobs are canceled as well:\n\n```scala 3\nval expectedQueue = structured {\n  val queue = new ConcurrentLinkedQueue[String]()\n  val job1 = fork {\n    val innerJob = fork {\n      fork {\n        delay(3.seconds)\n        println(\"inner-inner-Job\")\n        queue.add(\"inner-inner-Job\")\n      }\n      delay(2.seconds)\n      println(\"innerJob\")\n      queue.add(\"innerJob\")\n    }\n    delay(1.second)\n    queue.add(\"job1\")\n  }\n  val job = fork {\n    delay(500.millis)\n    job1.cancel()\n    queue.add(\"job2\")\n    43\n  }\n  queue\n}\nexpectedQueue.toArray should contain theSameElementsInOrderAs List(\"job2\")\n```\n\nTrying to get the value from a canceled job will throw an `InterruptedException`. However, joining a canceled job will not throw any exception.\n\n**You won't pay any additional cost for canceling a job**. The cancellation mechanism is based on the interruption of the virtual thread. No new structured scope is created for the cancellation mechanism.\n\n## Racing Jobs\n\nThe library provides the `race` method to race two jobs. The `race` function returns the result of the first job that completes. The other job is canceled. The following code snippet shows how to use the `race` method:\n\n```scala 3\nval results = new ConcurrentLinkedQueue[String]()\nval actual: Int | String = \n  race[Int, String](\n    {\n      delay(1.second)\n      results.add(\"job1\")\n      throw new RuntimeException(\"Error\")\n    }, {\n      delay(500.millis)\n      results.add(\"job2\")\n      \"42\"\n    }\n  )\nactual should be(\"42\")\nresults.toArray should contain theSameElementsInOrderAs List(\"job2\")\n```\n\nIf the first job completes with an exception, the `race` method waits for the second job to complete. and returns the result of the second job. If the second job completes with an exception, the `race` method throws the first exception it encountered.\n\nEach job adhere to the rules of structured concurrency. The `race` function is optimized. Every raced block creates more than one virtual thread under the hood, which should not be a problem for the Loom runtime.\n\n## Contributing\n\nIf you want to contribute to the project, please do it! Any help is welcome.\n\n## Acknowledgments\n\nThis project is inspired by the [Ox](https://github.com/softwaremill/ox) and\nthe [Unwrapped](https://github.com/xebia-functional/Unwrapped) libraries.\n\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frcardin%2Fsus4s","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frcardin%2Fsus4s","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frcardin%2Fsus4s/lists"}