{"id":22511134,"url":"https://github.com/renderedtext/wormhole","last_synced_at":"2025-08-21T00:30:59.601Z","repository":{"id":57556915,"uuid":"72004039","full_name":"renderedtext/wormhole","owner":"renderedtext","description":"Captures anything that is emitted from the callback - Elixir library repo","archived":false,"fork":false,"pushed_at":"2019-09-06T11:00:12.000Z","size":179,"stargazers_count":41,"open_issues_count":0,"forks_count":10,"subscribers_count":13,"default_branch":"master","last_synced_at":"2024-12-07T02:08:59.801Z","etag":null,"topics":["callback","callback-process","capture","elixir","semaphore-open-source","wormhole"],"latest_commit_sha":null,"homepage":"","language":"Elixir","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/renderedtext.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2016-10-26T13:07:52.000Z","updated_at":"2024-07-25T09:35:53.000Z","dependencies_parsed_at":"2022-09-16T16:14:34.217Z","dependency_job_id":null,"html_url":"https://github.com/renderedtext/wormhole","commit_stats":null,"previous_names":[],"tags_count":1,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/renderedtext%2Fwormhole","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/renderedtext%2Fwormhole/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/renderedtext%2Fwormhole/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/renderedtext%2Fwormhole/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/renderedtext","download_url":"https://codeload.github.com/renderedtext/wormhole/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":230471175,"owners_count":18231193,"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":["callback","callback-process","capture","elixir","semaphore-open-source","wormhole"],"created_at":"2024-12-07T02:09:10.947Z","updated_at":"2024-12-19T17:09:02.326Z","avatar_url":"https://github.com/renderedtext.png","language":"Elixir","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Wormhole\n\nWormhole captures anything that is emitted out of the callback\n(return value, error reason or process termination reason)\nand transfers it to the calling process\nin the form `{:ok, state}` or `{:error, reason}`.\nRead more in [description](#description)\n\n![wormhole](wormhole.jpg)\n\n## Difference between v1 and v2\nIn v1 callback that timed-out was left to run indefinitely.\nIn v2, callback is terminated when call times-out.\n[Read more...](docs/v1_vs_v2.md)\n\n\n## Installation\nAdd to the list of dependencies:\n```elixir\ndef deps do\n  [\n    {:wormhole, \"~\u003e 2.3\"}\n  ]\nend\n```\n\n## Examples\n\n### Successful execution - returning callback return value\nUnnamed function:\n```elixir\niex\u003e Wormhole.capture(fn-\u003e :a end)\n{:ok, :a}\n\n```\nNamed function without arguments:\n```elixir\niex\u003e Wormhole.capture(\u0026Process.list/0)\n{:ok, [#PID\u003c0.0.0\u003e, #PID\u003c0.3.0\u003e, #PID\u003c0.6.0\u003e, #PID\u003c0.7.0\u003e, ...]}\n```\nNamed function with arguments:\n```elixir\niex\u003e Wormhole.capture(Enum, :count, [[1,2,3]])\n{:ok, 3}\n```\n\nBoth versions with timeout explicitly set to 2 seconds:\n```elixir\niex\u003e Wormhole.capture(\u0026Process.list/0, timeout: 2_000)\n{:ok, [#PID\u003c0.0.0\u003e, #PID\u003c0.3.0\u003e, #PID\u003c0.6.0\u003e, #PID\u003c0.7.0\u003e, ...]}\n\niex\u003e Wormhole.capture(Enum, :count, [[1,2,3]], timeout: 2_000)\n{:ok, 3}\n```\n\n### Failed execution - returning failure reason\n```elixir\ndefmodule Test do\n  def f do\n    raise \"Hello\"\n  end\nend\n\niex\u003e Wormhole.capture(\u0026Test.f/0)\n{:error,\n {%RuntimeError{message: \"Hello\"},\n  [{Test, :f, 0, [file: 'iex', line: 23]},\n   {Wormhole, :\"-send_return_value/1-fun-0-\", 2,\n    [file: 'lib/wormhole.ex', line: 75]}]}}\n\niex\u003e Wormhole.capture(fn-\u003e throw :foo end)\n{:error,\n {{:nocatch, :foo},\n  [{Wormhole, :\"-send_return_value/1-fun-0-\", 2,\n    [file: 'lib/wormhole.ex', line: 75]}]}}\n\niex\u003e Wormhole.capture(fn-\u003e exit :foo end)\n{:error, :foo}\n\n```\n\n### Retry\n```elixir\niex\u003e Wormhole.capture(\u0026foo/0, [timeout: 2_000, retry_count: 3, backoff_ms: 300])\n```\n\n### Expecting ok-tuple\n```elixir\niex\u003e  Wormhole.capture(fn-\u003e {:ok, :a} end)\n{:ok, {:ok, :a}}\n\niex\u003e Wormhole.capture(fn-\u003e {:ok, :a} end, ok_tuple: true)\n{:ok, :a}\n\niex(3)\u003e Wormhole.capture(fn-\u003e :a end, ok_tuple: true)\n{:error, :a}\n```\n\n\n### Usage pattern\n```elixir\ndef ... do\n  ...\n  (\u0026some_function/0) |\u003e Wormhole.capture |\u003e some_function_response_handler\n  ...\nend\n\ndef some_function_response_handler({:ok, response}) do\n ...\nend\ndef some_function_response_handler({:error, error}) do\n ...\nend\n```\n\n## Description\nWormhole invokes `callback` in separate process and\nwaits for message from callback process containing callback return value.\nif callback is finished successfully the return value is propagated to the caller.\nIf callback process is terminated in any way (exception, signal, ...),\nerror reason is propagated to the caller.\n\nWormhole captures anything that is emitted out of the callback\n(return value, error reason or process termination reason)\nand transfers it to the calling process\nin the form `{:ok, state}` or `{:error, reason}`.\n\nBy default, any response coming from callback is accepted as successful and\nplaced within ok-tuple.\nIf option `ok_tuple` is set (meaning the callback is expected to return\nok-tuple), only ok-tuple response is considered successful.\nAny other response is treated as failure.\n\nIn case of failure, failure reason is logged with severity `warn`,\nunless option `skip_log` is set to true.\n\nIf `callback` execution is not finished within specified timeout,\n`callback` process is killed and error returned.\nDefault timeout value is specified in `@timeout`.\nUser can specify `timeout` in `options` keyword list.\n\nNote: `timeout_ms` is deprecated in favor of `timeout`.\n\nBy default if callback fails stacktrace will **not** be returned.\nUser can set `stacktrace` option to `true` and in that case stacktrace will\nbe returned in response.\nNote: `stacktrace` option works only if `crush_report` is not enabled.\n\nBy default there is no retry, but user can specify\n`retry_count` and `backoff_ms` in `options`.\nDefault back-off time value is specified in `@backoff_ms`.\n\nNote: `retry_count` specifies maximum number of times `callback` can be invoked.\nMore accurate name would be `try_count` but I think it would bring\nmore confusion than clarity, hence the name remains.\n\nBy default exceptions in callback-process are handled so that\nsupervisor does not generate CRUSH REPORT (when released - Exrm/Distillery).\nThis behavior can be overridden by setting `crush_report` to `true`.\nNote:\n  - Crush report is not generated in Elixir by default.\n  - Letting exceptions propagate might be useful for\n    some other applications too (e.g sentry client).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frenderedtext%2Fwormhole","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frenderedtext%2Fwormhole","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frenderedtext%2Fwormhole/lists"}