{"id":18406782,"url":"https://github.com/agentcoop/go-work","last_synced_at":"2025-06-20T06:05:21.681Z","repository":{"id":57558862,"uuid":"323065855","full_name":"AgentCoop/go-work","owner":"AgentCoop","description":"A concurrency pattern for Go","archived":false,"fork":false,"pushed_at":"2022-09-07T21:36:59.000Z","size":170,"stargazers_count":2,"open_issues_count":1,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-06-20T06:04:35.033Z","etag":null,"topics":["concurrency","design-patterns","go"],"latest_commit_sha":null,"homepage":"","language":"Go","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/AgentCoop.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}},"created_at":"2020-12-20T12:24:20.000Z","updated_at":"2024-07-12T18:13:28.000Z","dependencies_parsed_at":"2022-09-18T04:41:35.270Z","dependency_job_id":null,"html_url":"https://github.com/AgentCoop/go-work","commit_stats":null,"previous_names":[],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/AgentCoop/go-work","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AgentCoop%2Fgo-work","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AgentCoop%2Fgo-work/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AgentCoop%2Fgo-work/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AgentCoop%2Fgo-work/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/AgentCoop","download_url":"https://codeload.github.com/AgentCoop/go-work/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AgentCoop%2Fgo-work/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":260891145,"owners_count":23077909,"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":["concurrency","design-patterns","go"],"created_at":"2024-11-06T03:10:47.952Z","updated_at":"2025-06-20T06:05:16.672Z","avatar_url":"https://github.com/AgentCoop.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"## Introduction\nGo has lots of synchronization mechanisms ranging from language primitives such as mutexes, channels, atomic functions,\nand to a more complicated components such as [*context.Context*](https://golang.org/pkg/context/). Unfortunately,\nusing them to solve problems arising in day-to-day programming might be quite challenging, especially for novices.\n\nThe goal of the _Job design pattern_ and this implementation is to provide an easy-to-use and solid foundation\nfor solving problems involving concurrent executions and their control. The Job pattern in many cases can be viewed as\nan alternative to [*context.Context*](https://golang.org/pkg/context/), though it's not meant to completely replace it.   \n\n## Documentation\n### Why?\nI know, this is probably the most frequent question a skeptical programmer might ask. \"Why should I learn how to use it,\nwhen we already have [*context.Context*](https://golang.org/pkg/context/), your solution looks like an over-engineered one\".\nWell, because I have a weakness for Gimp, allow me to retort in a sarcastic way to such criticism:\n\u003cimg align=\"center\" style=\"margin: 6px\" src=\"https://raw.githubusercontent.com/AgentCoop/go-work/master/docs/funny-software-eng.png\" alt='funny Software Engineering' aria-label='' /\u003e\n\u003cbr\u003e\nBut seriously, simplicity in software engineering is often a reciprocal for productivity. Never underestimate the benefits\nof going one abstraction level up.\n\n#### What is a Job?\nA job is a set of concurrently running tasks, execution of which depends on each other. If one task fails, the whole job\nexecution fails too.\n```go\n    myjob := job.NewJob(nil)\n    myjob.AddTask(task1)\n    myjob.AddTask(task2)\n    \u003c-myjob.Run()\n    // Let's process the result\n```\nHere is a simple example: https://play.golang.org/p/vec2ybo3ti9. You can compare *context.Context* [example](https://golang.org/pkg/context/#example_WithCancel)\nwith the same implementation [using Job](https://play.golang.org/p/ge2FUO7a6WU).\n\n#### What is a Task?\nA single task consists of the three routines: an initialization routine, a recurrent routine, and a finalization routine:\n```go\nfunc (stream *stream) ReadOnStreamTask(j job.Job) (job.Init, job.Run, job.Finalize) {\n    init := func(task job.Task){\n        // Do some initialization\n    }\n    run := func(task job.Task) {\n        read(stream, task)\n        task.Tick()\n    }\n    fin := func(task job.Task) {\n        readCancel(stream, task)\n    }\n    return init, run, fin\n}\n```\nThe recurrent routine is running in an indefinite loop. It represents well-known `for { select {  } }` statements in\nGo. The recurrent routine calls three special methods:\n * **.Tick()** - to proceed task execution.\n * **.Done()** - to finish task execution (or **.FinishJob()** to finish job execution as well).\n * **.Idle()** - to tell that a task has nothing to do, and as a result it might be finished by reaching the idle timeout.\n\nTasks can assert some conditions, replacing `if err != nil { panic(err) }` by a more terse way:\n```go\nfunc (m *MyTask) MyTask(j job.Job) (job.Init, job.Run, job.Finalize) {\n    run := func(task *job.TaskInfo) {\n        _, err := os.Open(\"badfile\")\n        task.Assert(err)\n    }\n}\n```\nEvery failed assertion will result in the cancellation of job execution, and invocation of the finalization routines of all\ntasks of the job being cancelled.\n\nThere are two types of tasks: an ordinary task (or recurrent), and an oneshot task. The main purpose of a oneshot task\nis to start off execution of other recurrent tasks waiting for the oneshot to be finished. You probably heard of such\nan approach in software design as [Design by contract](https://en.wikipedia.org/wiki/Design_by_contract). Here we have\nsimilar approach: in the example below recurrent tasks make an assumption that they will be running when a network\nconnection is established and the goal of the oneshot task (_mngr.ConnectTask_) is to fulfill that precondition, providing\nreference to the connection in Job [value](#public-functions).\n```go\n    mainJob := j.NewJob(nil)\n    mainJob.AddOneshotTask(mngr.ConnectTask)\n    mainJob.AddTask(netmanager.ReadTask)\n    mainJob.AddTask(netmanager.WriteTask)\n    mainJob.AddTask(imgResizer.ScanForImagesTask)\n    mainJob.AddTask(imgResizer.SaveResizedImageTask)\n    \u003c-mainJob.Run()\n```\nA job can have only one oneshot task.\n\nFor data sharing tasks should employ (although it's not an obligation) a ping/pong synchronization using two channels,\nwhere the first one is being used to receive data and the second one - to notify the sender that data processing is completed.\n```go\n    run := func(task job.Task) {\n        select {\n        case data := \u003c- p.conn.Upstream().RecvRaw(): // Receive data from upstream server\n            p.conn.Downstream().Write() \u003c- data // Write data to downstream server\n            p.conn.Downstream().WriteSync() // sync with downstream data receiver\n            p.conn.Upstream().RecvRawSync() // sync with upstream data sender\n        }\n        task.Tick()\n    }\n```\n\nTo prevent a task from being blocked for good, be sure to close all channels it's using in its finalization routine.\n\n### Real life example\nNow, when you have a basic understanding, let's put the given pattern to use and take a look at a\n[real life example](https://github.com/AgentCoop/go-work-tcpbalancer):\nthe implementation of a reverse proxy working as layer 4 load balancer, a backend server resizing images, and a simple\nclient that would scan a specified directory for images and send them through the proxy server for resizing.\nThe code will speak for itself, so that you will quickly get the idea of how to use the given pattern.\n\n### API reference\n##### Public functions and variables\n  * [_NewJob(value **interface{}**) ***job**_](#job-value) - creates a new job with the given job value.\n  * RegisterDefaultLogger(logger **Logger**) - registers a fallback logger for all jobs.\n  * DefaultLogLevel **int**\n##### Job\n  * [_AddTask(job **JobTask**) ***task**_](docs/job.md) - adds a new task to the job\n  * [_GetTaskByIndex(index **int**) ***task**_](docs/job.md) - returns a task by the given index.\n  * [_AddOneshotTask(job **JobTask**)_](docs/job.md) - adds an oneshot tasks to the job\n  * [_AddTaskWithIdleTimeout(job **JobTask**, timeout **time.Duration**) ***task**_](docs/job.md) - adds a task with an idle timeout\n  * [_WithTimeout(duration **time.Duration**)_](docs/job.md) - sets run timeout for the job. \n  * [_Run() **chan struct{}**_](docs/job.md) - starts the execution of the tasks.\n  * [_RunInBackground() **\u003c-chan struct{}**_](docs/job.md) - runs job's tasks in background. An oneshot task is required.\n  Receives a signal from the returned channel once the oneshot task is finished.\n  * [_Cancel(**err interface{}**)_](docs/job.md) - cancels the job with the given error.\n  * [_Finish()_](docs/job.md) - finishes the job.\n  * [_Log(level **int**) **chan\u003c- interface{}**_](docs/job.md) - writes a log record using fallback or job's logger.\n  * [_RegisterLogger(logger **Logger**)_](docs/job.md) - registers a job logger.\n  * [_GetValue() **interface{}**_](docs/job.md) - returns a job value.\n  * [_SetValue(v **interface{}**)_](docs/job.md) - sets a job value.\n  * [_GetInterruptedBy() (***task, interface{}**)_](docs/job.md) - returns a task and an error that interrupted the job execution.\n  * [_GetState() **JobState**_](docs/job.md) - returns the job state.\n  *\t[_TaskDoneNotify() **\u003c-chan \\*task**_](docs/job.md) -  receives **\\*task** from the returned channel when a task is finished.\n  *\t[_JobDoneNotify() **chan struct{}**_](docs/job.md)- receives a signal from the returned channel when the job is finished\n##### Task\n  * [_GetIndex() **int**_](docs/task.md) - returns task index. Oneshot tasks have predefined 0-index.\n  * [_GetJob() **Job**_](docs/task.md) - returns a reference to the task's job.\n  * [_GetState() **TaskState**_](docs/task.md) - returns task state.\n  * [_GetResult() **interface{}**_](docs/task.md) - returns task result.\n  * [_SetResult(result **interface{}**)_](docs/task.md) - sets task result.\n  * [_Tick()_](docs/task.md)\u003csup\u003e1\u003c/sup\u003e - proceeds task execution. \n  * [_Done()_](docs/task.md)\u003csup\u003e1\u003c/sup\u003e - marks task as finished and stops its execution.\n  * [_Idle()_](docs/task.md)\u003csup\u003e1\u003c/sup\u003e - do nothing. Being used for tasks having idle timeouts.\n  * [_FinishJob()_](docs/task.md) \n  * [_Assert(err **interface{}**)_](docs/task.md) - asserts that error has zero value.\n  * [_AssertTrue(cond **bool**, err **string**)_](docs/task.md) - asserts that condition is evaluated to _true_, otherwise stops\n  * [_AssertNotNil(value **interface{}**)_](docs/task.md)\n \n###### Footnotes:\n 1. Being called by the recurrent routine.","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fagentcoop%2Fgo-work","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fagentcoop%2Fgo-work","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fagentcoop%2Fgo-work/lists"}