{"id":16964973,"url":"https://github.com/propensive/parasite","last_synced_at":"2025-04-11T23:02:45.366Z","repository":{"id":50367306,"uuid":"518857672","full_name":"propensive/parasite","owner":"propensive","description":"Structured asynchronous task management in Scala","archived":false,"fork":false,"pushed_at":"2025-02-02T20:17:35.000Z","size":1483,"stargazers_count":3,"open_issues_count":5,"forks_count":0,"subscribers_count":4,"default_branch":"main","last_synced_at":"2025-02-20T01:38:46.035Z","etag":null,"topics":["async","asynchronous","concurrency","scala","task","threads","virtual-threads"],"latest_commit_sha":null,"homepage":"https://soundness.dev/parasite/","language":"Scala","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/propensive.png","metadata":{"files":{"readme":".github/readme.md","changelog":null,"contributing":".github/contributing.md","funding":null,"license":null,"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":"2022-07-28T13:28:23.000Z","updated_at":"2025-02-02T20:17:38.000Z","dependencies_parsed_at":"2023-11-16T09:26:16.849Z","dependency_job_id":"2b019614-99bb-4d18-8598-66e20d68bf9a","html_url":"https://github.com/propensive/parasite","commit_stats":null,"previous_names":["propensive/parasite","propensive/parasitism"],"tags_count":19,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/propensive%2Fparasite","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/propensive%2Fparasite/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/propensive%2Fparasite/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/propensive%2Fparasite/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/propensive","download_url":"https://codeload.github.com/propensive/parasite/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":239805873,"owners_count":19700199,"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":["async","asynchronous","concurrency","scala","task","threads","virtual-threads"],"created_at":"2024-10-13T23:44:44.051Z","updated_at":"2025-02-20T08:31:38.138Z","avatar_url":"https://github.com/propensive.png","language":"Scala","funding_links":[],"categories":[],"sub_categories":[],"readme":"[\u003cimg alt=\"GitHub Workflow\" src=\"https://img.shields.io/github/actions/workflow/status/propensive/parasite/main.yml?style=for-the-badge\" height=\"24\"\u003e](https://github.com/propensive/parasite/actions)\n[\u003cimg src=\"https://img.shields.io/discord/633198088311537684?color=8899f7\u0026label=DISCORD\u0026style=for-the-badge\" height=\"24\"\u003e](https://discord.com/invite/MBUrkTgMnA)\n\u003cimg src=\"/doc/images/github.png\" valign=\"middle\"\u003e\n\n# Parasite\n\n__Safe, structured asynchronous tasks__\n\n_Parasite_ provides a safe interface for working with structured concurrency,\nbuilt upon Java platform or virtual threads.\nAsynchronous tasks form a supervisor hierarchy, where each task is\nowned by a parent task which becomes bound to its lifecycle.\n\n## Features\n\n- simple interface for creating and running tasks\n- supports platform or virtual threads\n- asynchronous tasks form an intuitive hierarchy\n- safe hierarchical cancellation\n- ready for capture checking for improved resource safety\n\n\n## Availability\n\nTBC\n\n\n\n\n\n\n## Getting Started\n\nAll Parasite terms and types are defined in the `parasite` package:\n```scala\nimport parasite.*\n```\nand are exported to `soundness`, so you can alternatively just:\n```scala\nimport soundness.*\n```\n\nA Parasite task, an instance of `Task`, is similar to a Scala or Java\n`Future`: it encapsulates a block of code which it starts executing\nimmediately, and once evaluated, holds the result. There are, however, a few\ndifferences.\n\nWe can create one inside a `supervise` block with:\n```scala\nimport soundness.*\n\nimport strategies.throwUnsafely\nimport threadingModels.virtual\n\ndef run() = supervise:\n  val task = async:\n    delay(60*Second)\n    1000\n```\n\nThis creates a new `Task[Int]` where `Int` comes from the result of evaluating the `async`\nbody.\n\nThis will create *and start* the new task in a thread forked from the current\nthread. Execution of `someSlowTask()` will proceed in a forked thread until it\ncompletes.\n\nWe can call `task.await()` in the current thread to wait until the forked\nthread finishes, and return the value which results from its execution.\n\n\n\nAsynchronous tasks form a hierarchy. A new task may be started within the body of an\nexisting task, and the new task will be established a child of the latter. This\nrelationship does not change over the lifetime of the child task.\n\nParenthood is conveyed to a new child task through a `Monitor` instance, which is\nintroduced as a contextual instance in the body of an `async` or `supervise` block.\n\nWhile both `async` and `supervise` provide `Monitor`s to their contexts, `async`\nadditionally _consumes_ a `Monitor` whereas `supervise` does not. Thus, `supervise` is\nintended to be used at the top of an asynchronous hierarchy, while `async` blocks\ndelimit the branches of the hierarchy beneath it. Only `supervise` can create new\n`Monitor`s without an existing `Monitor`, so every `async` must ultimately stem\nfrom a `supervise` block.\n\nHere's an example:\n\n```scala\nsupervise:\n  val task1: Task[Text] = async:\n    val taskA: Task[Double] = async(readNumber())\n    val taskB: Task[Double] = async(readNumber())\n\n    t\"The result is ${taskA.await() + taskB.await()}\"\n\n  Out.println(t\"Running the task\")\n  val message = task1.await()\n  Out.println(message)\n```\n\nThis example includes three `async` blocks and a `supervise` block. The outer `supervise`\ncreates a new contextual `Monitor` from nothing. The `async` block in `task1` uses this\n`Monitor` and introduces a new `Monitor`, linked to the first. And the `async` blocks in\n`taskA` and `taskB` both use this child monitor and introduce their own `Monitor` instances.\n\nNote that while the contextual `Monitor` instances of `supervise` _and_ the `Monitor` of\n`task1`'s `async` block are in-scope when `readNumber()` is called, the latter has higher\npriority because it is more deeply nested. This is exactly what we need.\n\nUnlike an `async` block, which immediately returns a `Task`, and executes concurrently,\n`supervise` runs synchronously, and returns a direct value.\n\nThe relationships between child and parent tasks constrain their lifecycles. A task may not\nfinish (that is, produce a result or be cancelled) until its children have also finished.\n\nThis has a few implications.\n\nUsually, tasks will be `await`ed within the body in which they are created, but this is\nnot required, and it is not considered an error if a task is never explicitly awaited.\n\nFor example in,\n```scala\nsupervise:\n  val parent: Task[Text] = async:\n    val child = async(slowOperation())\n    t\"finished\"\n  Out.println(parent.await())\n```\na `child` task is started, but there's no explicit call to `child.await()`.\n\nSo despite reaching the return value `t\"finished\"` almost immediately, since `slowOperation()`\nis running concurrently, the call to `parent.await()` will not return it until `child` has\nfinished executing.\n\nThis is important to maintain the invariant that following the completion of a task\n(by `await`ing it), no further execution can take place on resources captured in its body—and\nallowed to escape through a child task.\n\nHere's an example where this matters:\n```scala\nsupervise:\n  val log = Log(p\"/var/log/app.log\")\n  val parent: Task[Text] = async:\n    log.info(t\"Starting\")\n\n    async:\n      for i \u003c- 0 to 10 do\n        delay(1*Second)\n        log.info(t\"Still running\")\n\n    t\"complete\"\n\n  log.info(parent.await())\n  log.close()\n```\n\nAt the point `parent.await()` is called, the child task\nwill have been spawned and no long-running tasks stand in the way of the result `t\"complete\"`\nbeing evaluated.\n\nBut the child task will keep running because it's in an infinite loop. _If_ we were to permit\nthe result to be returned right away, logged, and the logger `close`d, then the continued\nexecution of the child task would be problematic: it would attempt to log after the log is\nclosed.\n\nSo instead, the call to `parent.await()` will not return until the child task finishes.\n\n### Termination\n\nThe child task runs continuously for ten seconds. Does that mean the call to `parent.await()` will\ntake ten seconds too? That depends!\n\nThree options are available, as contextual values:\n- `asyncTermination.await` will wait for all child tasks to terminate,\n- `asyncTermination.cancel` will cancel the child asks at the first opportunity, and\n- `asyncTermination.fail` will cause the call to `parent.await()` to throw an error\n  (which must be handled) if any incomplete tasks exist when the parent task returns.\n- `asyncTermination.panic` behaves like `asyncTermination.fail`, but throws an unchecked `Panic`\n  error.\n\nEach of these options may be more useful in different scenarios.\n\nFor example, if child tasks are involved in modifying mutable state (e.g. on disk), and an\nincomplete write could result in data being in an inconsistent state, then it might be better\nto use `await` to ensure each child task completes.\n\nWhereas pure operations without side-effects should be able to use `cancel` to terminate\nchild tasks without consequences.\n\nIf our intention was to explicitly await every child task we spawn, by design, then we may wish\nto make it an error if we forget to await one task. In this case, we can fail with a checked or\nan unchecked exception.\n\n### Cancellation\n\nWe can cancel a running task by calling its `cancel()` method. This does, however, require\nthe child's cooperation: a child task should call `relent()` every so often, at a point where\nit would be safe to stop execution, if the task has been cancelled from another thread.\n\nAdapting the example above, we could add a call to `relent()` to the infinite loop to check\nabort execution if the task has been cancelled.\n\n\n```scala\nval task = async:\n  for i \u003c- 0 to 10 do\n    delay(1*Second)\n    relent()\n    log.info(t\"Still running\")\n```\n\nNow, if `task` is cancelled because its parent task finishes before it, when it reaches the call\nto `relent()`, it was cease execution. The `log.info` on the next line will not be executed, and\nit will not complete with a result. But since, by definition, this value was never `await`ed\nanyway, the absence of a result is opaque.\n\nDespite the cancellation, the method will nevertheless delay for a full second, because `delay`\nis uninterruptible. But alternatives exist.\n\n### Snoozing, sleeping, pausing and hibernating\n\nFour methods, `snooze`, `sleep`, `delay` and `hibernate` allow execution of the current\nthread to stop temporarily. Between them, they provide _interruptible_ or _uninterruptible_\npauses for a time _duration_ or until an _instant_. Here, \"interruptible\" means that the pause\nmay end sooner if the paused task is cancelled.\n\n- `snooze` and `sleep` are interruptible\n- `delay` and `hibernate` are not interruptible\n- `snooze` and `delay` take a time duration\n- `sleep` and `hibernate` take an instant in time when they should wake up\n\nTo help remember the names,\n- A `snooze` is a short delay of light sleep, like the few extra minutes (a fixed duration) of sleep\n  the \"snooze\" button on an alarm clock offers.\n- A `sleep` usually involves waking up at a particular time in the morning, whatever time of the evening\n  it starts; but it's still possible to be woken in the night.\n- An animal will `hibernate` until a particular time in the spring, and can't easily be woken.\n- If a train experiences a `delay`, it is usually expressed as a duration of time (for example, \"a\n  five-minute delay\"), but once there's a delay, it can't be \"cancelled\" to magically make the\n  train on-time again.\n\n### `Task` methods\n\nAn `Async` instance has several useful methods:\n- `await()`, which blocks the current thread until the `Async` produces a value\n- `await(timeout)`, which takes a `timeout`, after which a `TimeoutError` will\n  be thrown if no value has been produced\n- `map` and `flatMap`, providing standard monadic operations on the `Async`\n- `cancel()`, which will stop the task running\n\n### Platform or Virtual threading\n\nFrom Java 21 and above, threads may be either _platform_ (corresponding to\nthreads managed by the operating system) or _virtual_ (managed by the JVM).\nPrior to Java 21, all threads are _platform threads_. Parasite needs to know\nwhich type of thread to use, and this requires one of three imports:\n- `threadingModels.virtual`\n- `threadingModels.platform`\n- `threadingModels.adaptive`\n\nNote that choosing `threadingModels.virtual` will result an a runtime error on JDKs\nolder than Java 21. To avoid this, use `threadingModels.adaptive` which will fall\nback to platform threads on earlier JVMs.\n\n### Cancellation\n\nA task may be cancelled at any time, though cancellation requires cooperation from the task:\nthe task's body must be written to expect possible cancellation.\n\nWithin a task's body, the method `relent()` may be called multiple times. For\nas long as the task is running normally, `relent()` will do nothing. But if a\ntask is cancelled, the task will stop immediately, without a value being\nproduced. Any `await()` calls on the task will throw a `CancelError`.\n\nHowever, this happens _only_ when `relent()` is called, so if no such calls\nare run as the task is executing, that task cannot be cancelled, and it must\nexecute to completion.\n\n\n## Status\n\nParasite is classified as __fledgling__. For reference, Soundness projects are\ncategorized into one of the following five stability levels:\n\n- _embryonic_: for experimental or demonstrative purposes only, without any guarantees of longevity\n- _fledgling_: of proven utility, seeking contributions, but liable to significant redesigns\n- _maturescent_: major design decisions broady settled, seeking probatory adoption and refinement\n- _dependable_: production-ready, subject to controlled ongoing maintenance and enhancement; tagged as version `1.0.0` or later\n- _adamantine_: proven, reliable and production-ready, with no further breaking changes ever anticipated\n\nProjects at any stability level, even _embryonic_ projects, can still be used,\nas long as caution is taken to avoid a mismatch between the project's stability\nlevel and the required stability and maintainability of your own project.\n\nParasite is designed to be _small_. Its entire source code currently consists\nof 736 lines of code.\n\n## Building\n\nParasite will ultimately be built by Fury, when it is published. In the\nmeantime, two possibilities are offered, however they are acknowledged to be\nfragile, inadequately tested, and unsuitable for anything more than\nexperimentation. They are provided only for the necessity of providing _some_\nanswer to the question, \"how can I try Parasite?\".\n\n1. *Copy the sources into your own project*\n   \n   Read the `fury` file in the repository root to understand Parasite's build\n   structure, dependencies and source location; the file format should be short\n   and quite intuitive. Copy the sources into a source directory in your own\n   project, then repeat (recursively) for each of the dependencies.\n\n   The sources are compiled against the latest nightly release of Scala 3.\n   There should be no problem to compile the project together with all of its\n   dependencies in a single compilation.\n\n2. *Build with [Wrath](https://github.com/propensive/wrath/)*\n\n   Wrath is a bootstrapping script for building Parasite and other projects in\n   the absence of a fully-featured build tool. It is designed to read the `fury`\n   file in the project directory, and produce a collection of JAR files which can\n   be added to a classpath, by compiling the project and all of its dependencies,\n   including the Scala compiler itself.\n   \n   Download the latest version of\n   [`wrath`](https://github.com/propensive/wrath/releases/latest), make it\n   executable, and add it to your path, for example by copying it to\n   `/usr/local/bin/`.\n\n   Clone this repository inside an empty directory, so that the build can\n   safely make clones of repositories it depends on as _peers_ of `parasite`.\n   Run `wrath -F` in the repository root. This will download and compile the\n   latest version of Scala, as well as all of Parasite's dependencies.\n\n   If the build was successful, the compiled JAR files can be found in the\n   `.wrath/dist` directory.\n\n## Contributing\n\nContributors to Parasite are welcome and encouraged. New contributors may like\nto look for issues marked\n[beginner](https://github.com/propensive/parasite/labels/beginner).\n\nWe suggest that all contributors read the [Contributing\nGuide](/contributing.md) to make the process of contributing to Parasite\neasier.\n\nPlease __do not__ contact project maintainers privately with questions unless\nthere is a good reason to keep them private. While it can be tempting to\nrepsond to such questions, private answers cannot be shared with a wider\naudience, and it can result in duplication of effort.\n\n## Author\n\nParasite was designed and developed by Jon Pretty, and commercial support and\ntraining on all aspects of Scala 3 is available from [Propensive\nO\u0026Uuml;](https://propensive.com/).\n\n\n\n## Name\n\nA tick indicates the completion of a task, while also being the name of a common human _parasite_, hence the name, and the allusion to the parasitic nature of threads.\n\nIn general, Soundness project names are always chosen with some rationale,\nhowever it is usually frivolous. Each name is chosen for more for its\n_uniqueness_ and _intrigue_ than its concision or catchiness, and there is no\nbias towards names with positive or \"nice\" meanings—since many of the libraries\nperform some quite unpleasant tasks.\n\nNames should be English words, though many are obscure or archaic, and it\nshould be noted how willingly English adopts foreign words. Names are generally\nof Greek or Latin origin, and have often arrived in English via a romance\nlanguage.\n\n## Logo\n\nThe logo shows a tick symbol, indicative of a task (which has been completed).\n\n## License\n\nParasite is copyright \u0026copy; 2025 Jon Pretty \u0026 Propensive O\u0026Uuml;, and\nis made available under the [Apache 2.0 License](/license.md).\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpropensive%2Fparasite","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpropensive%2Fparasite","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpropensive%2Fparasite/lists"}