• Stars
    star
    6,764
  • Rank 5,836 (Top 0.2 %)
  • Language
    Go
  • License
    MIT License
  • Created almost 2 years ago
  • Updated over 1 year ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Better structured concurrency for go

conch

conc: better structured concurrency for go

Go Reference Sourcegraph Go Report Card codecov Discord

conc is your toolbelt for structured concurrency in go, making common tasks easier and safer.

go get github.com/sourcegraph/conc

At a glance

All pools are created with pool.New() or pool.NewWithResults[T](), then configured with methods:

Goals

The main goals of the package are:

  1. Make it harder to leak goroutines
  2. Handle panics gracefully
  3. Make concurrent code easier to read

Goal #1: Make it harder to leak goroutines

A common pain point when working with goroutines is cleaning them up. It's really easy to fire off a go statement and fail to properly wait for it to complete.

conc takes the opinionated stance that all concurrency should be scoped. That is, goroutines should have an owner and that owner should always ensure that its owned goroutines exit properly.

In conc, the owner of a goroutine is always a conc.WaitGroup. Goroutines are spawned in a WaitGroup with (*WaitGroup).Go(), and (*WaitGroup).Wait() should always be called before the WaitGroup goes out of scope.

In some cases, you might want a spawned goroutine to outlast the scope of the caller. In that case, you could pass a WaitGroup into the spawning function.

func main() {
    var wg conc.WaitGroup
    defer wg.Wait()

    startTheThing(&wg)
}

func startTheThing(wg *conc.WaitGroup) {
    wg.Go(func() { ... })
}

For some more discussion on why scoped concurrency is nice, check out this blog post.

Goal #2: Handle panics gracefully

A frequent problem with goroutines in long-running applications is handling panics. A goroutine spawned without a panic handler will crash the whole process on panic. This is usually undesirable.

However, if you do add a panic handler to a goroutine, what do you do with the panic once you catch it? Some options:

  1. Ignore it
  2. Log it
  3. Turn it into an error and return that to the goroutine spawner
  4. Propagate the panic to the goroutine spawner

Ignoring panics is a bad idea since panics usually mean there is actually something wrong and someone should fix it.

Just logging panics isn't great either because then there is no indication to the spawner that something bad happened, and it might just continue on as normal even though your program is in a really bad state.

Both (3) and (4) are reasonable options, but both require the goroutine to have an owner that can actually receive the message that something went wrong. This is generally not true with a goroutine spawned with go, but in the conc package, all goroutines have an owner that must collect the spawned goroutine. In the conc package, any call to Wait() will panic if any of the spawned goroutines panicked. Additionally, it decorates the panic value with a stacktrace from the child goroutine so that you don't lose information about what caused the panic.

Doing this all correctly every time you spawn something with go is not trivial and it requires a lot of boilerplate that makes the important parts of the code more difficult to read, so conc does this for you.

stdlib conc
type caughtPanicError struct {
    val   any
    stack []byte
}

func (e *caughtPanicError) Error() string {
    return fmt.Sprintf(
        "panic: %q\n%s",
        e.val,
        string(e.stack)
    )
}

func main() {
    done := make(chan error)
    go func() {
        defer func() {
            if v := recover(); v != nil {
                done <- &caughtPanicError{
                    val: v,
                    stack: debug.Stack()
                }
            } else {
                done <- nil
            }
        }()
        doSomethingThatMightPanic()
    }()
    err := <-done
    if err != nil {
        panic(err)
    }
}
func main() {
    var wg conc.WaitGroup
    wg.Go(doSomethingThatMightPanic)
    // panics with a nice stacktrace
    wg.Wait()
}

Goal #3: Make concurrent code easier to read

Doing concurrency correctly is difficult. Doing it in a way that doesn't obfuscate what the code is actually doing is more difficult. The conc package attempts to make common operations easier by abstracting as much boilerplate complexity as possible.

Want to run a set of concurrent tasks with a bounded set of goroutines? Use pool.New(). Want to process an ordered stream of results concurrently, but still maintain order? Try stream.New(). What about a concurrent map over a slice? Take a peek at iter.Map().

Browse some examples below for some comparisons with doing these by hand.

Examples

Each of these examples forgoes propagating panics for simplicity. To see what kind of complexity that would add, check out the "Goal #2" header above.

Spawn a set of goroutines and waiting for them to finish:

stdlib conc
func main() {
    var wg sync.WaitGroup
    for i := 0; i < 10; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            // crashes on panic!
            doSomething()
        }()
    }
    wg.Wait()
}
func main() {
    var wg conc.WaitGroup
    for i := 0; i < 10; i++ {
        wg.Go(doSomething)
    }
    wg.Wait()
}

Process each element of a stream in a static pool of goroutines:

stdlib conc
func process(stream chan int) {
    var wg sync.WaitGroup
    for i := 0; i < 10; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            for elem := range stream {
                handle(elem)
            }
        }()
    }
    wg.Wait()
}
func process(stream chan int) {
    p := pool.New().WithMaxGoroutines(10)
    for elem := range stream {
        elem := elem
        p.Go(func() {
            handle(elem)
        })
    }
    p.Wait()
}

Process each element of a slice in a static pool of goroutines:

stdlib conc
func process(values []int) {
    feeder := make(chan int, 8)

    var wg sync.WaitGroup
    for i := 0; i < 10; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            for elem := range feeder {
                handle(elem)
            }
        }()
    }

    for _, value := range values {
        feeder <- value
    }
    close(feeder)
    wg.Wait()
}
func process(values []int) {
    iter.ForEach(values, handle)
}

Concurrently map a slice:

stdlib conc
func concMap(
    input []int,
    f func(int) int,
) []int {
    res := make([]int, len(input))
    var idx atomic.Int64

    var wg sync.WaitGroup
    for i := 0; i < 10; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()

            for {
                i := int(idx.Add(1) - 1)
                if i >= len(input) {
                    return
                }

                res[i] = f(input[i])
            }
        }()
    }
    wg.Wait()
    return res
}
func concMap(
    input []int,
    f func(*int) int,
) []int {
    return iter.Map(input, f)
}

Process an ordered stream concurrently:

stdlib conc
func mapStream(
    in chan int,
    out chan int,
    f func(int) int,
) {
    tasks := make(chan func())
    taskResults := make(chan chan int)

    // Worker goroutines
    var workerWg sync.WaitGroup
    for i := 0; i < 10; i++ {
        workerWg.Add(1)
        go func() {
            defer workerWg.Done()
            for task := range tasks {
                task()
            }
        }()
    }

    // Ordered reader goroutines
    var readerWg sync.WaitGroup
    readerWg.Add(1)
    go func() {
        defer readerWg.Done()
        for result := range taskResults {
            item := <-result
            out <- item
        }
    }()

    // Feed the workers with tasks
    for elem := range in {
        resultCh := make(chan int, 1)
        taskResults <- resultCh
        tasks <- func() {
            resultCh <- f(elem)
        }
    }

    // We've exhausted input.
    // Wait for everything to finish
    close(tasks)
    workerWg.Wait()
    close(taskResults)
    readerWg.Wait()
}
func mapStream(
    in chan int,
    out chan int,
    f func(int) int,
) {
    s := stream.New().WithMaxGoroutines(10)
    for elem := range in {
        elem := elem
        s.Go(func() stream.Callback {
            res := f(elem)
            return func() { out <- res }
        })
    }
    s.Wait()
}

Status

This package is currently pre-1.0. There are likely to be minor breaking changes before a 1.0 release as we stabilize the APIs and tweak defaults. Please open an issue if you have questions, concerns, or requests that you'd like addressed before the 1.0 release. Currently, a 1.0 is targeted for March 2023.

More Repositories

1

sourcegraph

Code AI platform with Code Search & Cody
Go
9,777
star
2

checkup

Distributed, lock-free, self-hosted health checks and status pages
Go
3,392
star
3

cody

Type less, code more: Cody is an AI code assistant that uses advanced search and codebase context to help you write and fix code.
TypeScript
2,348
star
4

thyme

Automatically track which applications you use and for how long.
Go
2,237
star
5

appdash

Application tracing system for Go, based on Google's Dapper.
Go
1,720
star
6

webloop

WebLoop: Scriptable, headless WebKit with a Go API. Like PhantomJS, but for Go.
Go
1,357
star
7

go-langserver

Go language server to add Go support to editors and other tools that use the Language Server Protocol (LSP)
Go
1,163
star
8

srclib

srclib is a polyglot code analysis library, built for hackability. It consists of language analysis toolchains (currently for Go and Java, with Python, JavaScript, and Ruby in beta) with a common output format, and a CLI tool for running the analysis.
Go
944
star
9

doctree

First-class library documentation for every language (based on tree-sitter), with symbol search & more. Lightweight single binary, run locally or self-host. Surfaces usage examples via Sourcegraph.
Go
848
star
10

javascript-typescript-langserver

JavaScript and TypeScript code intelligence through the Language Server Protocol
TypeScript
792
star
11

sg.nvim

Experimental Sourcegraph + Cody plugin for Neovim
Lua
707
star
12

go-diff

Unified diff parser and printer for Go
Go
407
star
13

thesrc

Example of a 3-layer (frontend, API, datastore) Go web app (based on the code that powers https://sourcegraph.com)
Go
396
star
14

go-selenium

Selenium WebDriver client for Go
Go
364
star
15

go-webkit2

WebKit API bindings (WebKitGTK+ v2) for Go
Go
305
star
16

syntaxhighlight

Go package for syntax highlighting of code
Go
263
star
17

src-cli

Sourcegraph CLI
Go
261
star
18

scip

SCIP Code Intelligence Protocol
Go
257
star
19

zoekt

Fast trigram based code search
Go
229
star
20

prototools

documentation generator & other tools for protobuf/gRPC
Go
162
star
21

jsonrpc2

Package jsonrpc2 provides a client and server implementation of JSON-RPC 2.0 (http://www.jsonrpc.org/specification)
Go
159
star
22

awesome-code-ai

A list of AI coding tools (assistants, completions, refactoring, etc.)
157
star
23

careers

Want to work at Sourcegraph?
132
star
24

openctx

See contextual info about code from your dev tools, in your editor, code review, and anywhere else you read code.
TypeScript
122
star
25

lsif-go

Language Server Indexing Format (LSIF) generator for Go
Go
111
star
26

deploy-sourcegraph

Deploy Sourcegraph to a Kubernetes cluster for large-scale code search and intelligence
Shell
104
star
27

python-langserver

Language server which talks LSP via JSONRPC for Python.
Python
102
star
28

emacs-lsp

LSP support for Emacs
Emacs Lisp
102
star
29

apiproxy

apiproxy proxies HTTP/REST APIs with configurable cache timeouts, etc.
Go
96
star
30

syntect_server

HTTP code syntax highlighting server written in Rust.
Rust
89
star
31

handbook

๐Ÿ“˜ The Sourcegraph handbook
TypeScript
87
star
32

llmsp

LLM-power language server protocol implementation.
Go
85
star
33

about

Sourcegraph blog, feature announcements, and website (about.sourcegraph.com)
TypeScript
84
star
34

sourcegraph-vscode-DEPRECATED

*๏ธโƒฃ+ ๐Ÿ†š = โค๏ธ
TypeScript
83
star
35

go-vcs

manipulate and inspect VCS repositories in Go
Go
77
star
36

deploy-sourcegraph-docker

Sourcegraph with Docker Compose deployment reference
Shell
73
star
37

go-lsp

Go types for the messages used in the Language Server Protocol.
Go
71
star
38

scip-java

SCIP Code Intelligence Protocol (LSIF) generator for Java
Java
60
star
39

docsite

The documentation site used by Sourcegraph
Go
57
star
40

codenotify

Go
55
star
41

codeintellify

Adds code intelligence to code views on the web โœจ
TypeScript
55
star
42

jetbrains

Kotlin
54
star
43

browser-extensions

Sourcegraph's browser extensions: MOVED. See https://docs.sourcegraph.com/integration/browser_extension.
TypeScript
49
star
44

go-ses

Amazon AWS Simple Email Service (SES) client for Go
Go
49
star
45

emacs-cody

Sourcegraph Cody in Emacs
Emacs Lisp
48
star
46

sourcegraph-extension-api

Sourcegraph extension API: use and build extensions that enhance reading and reviewing code in your existing tools. "The extension API you wish your code host had."
TypeScript
44
star
47

srclib-cpp

43
star
48

go-sourcegraph

Go
43
star
49

scip-typescript

SCIP indexer for TypeScript and JavaScript
TypeScript
43
star
50

go-template-lint

Linter for Go text/template (and html/template) template files
Go
40
star
51

android-sdk-jars

HTML
40
star
52

run

๐Ÿƒโ€โ™‚๏ธ A new way to execute commands and manipulate command output in Go
Go
40
star
53

codesearch.ai

codesearch.ai semantic code search engine
Go
36
star
54

sourcegraph-jetbrains

Sourcegraph for JetBrains IDEs (IntelliJ)
Java
36
star
55

docs

Web app and content for the new Sourcegraph documentation.
TypeScript
35
star
56

lsif-node

Language Server Indexing Format (LSIF) generator for JavaScript and TypeScript
TypeScript
35
star
57

sourcegraph-typescript

Provides code intelligence for TypeScript
TypeScript
34
star
58

lsif-py

Language Server Indexing Format (LSIF) generator for Python
Python
31
star
59

srclib-c

Makefile
30
star
60

srclib-go

Go toolchain for srclib
Go
29
star
61

lsp-adapter

lsp-adapter provides a proxy which adapts Sourcegraph LSP requests to vanilla LSP requests
Go
29
star
62

s3cache

Amazon S3 storage interface for a Go cache
Go
29
star
63

srclib-python

Python
28
star
64

pydep

a simple command line tool / package that prints the dependencies of a python project
Python
28
star
65

app

Issue tracker for the Sourcegraph app - a lightweight single-binary version of Sourcegraph for your local machine
27
star
66

sg

sg releases
27
star
67

helix-docker

Docker images for Perforce Helix
Dockerfile
27
star
68

code-intel-extensions

Provides precise code intelligence via LSIF and Language Servers, and fuzzy code intelligence using ctags and text search
TypeScript
26
star
69

scip-clang

C++
25
star
70

makex

makex is a "make" clone for Go that makes it easier to write build tools in Go. It lets you define tasks and dependencies in the familiar Makefile format, and unlike just shelling out to "make", it gives you programmatic access (in Go) to the progress and console output of your tasks.
Go
24
star
71

scip-python

SCIP indexer for Python
Python
23
star
72

codemod

A collection of codemods powered by TS-Morph and PostCSS
TypeScript
23
star
73

jsonx

Extended JSON parser and writer for Go
Go
21
star
74

srclib-csharp

placeholder for a srclib C# toolchain
C#
20
star
75

learn

Sourcegraph Learn: an educational hub to support all developers
TypeScript
19
star
76

lsif-jsonnet

Language Server Index Format (LSIF) generator for JSonnet
Go
19
star
77

vcsstore

vcsstore stores VCS repositories and makes them accessible via HTTP
Go
19
star
78

sourcegraph-sublime

Sourcegraph for Sublime Text 3
Python
18
star
79

sourcegraph-git-extras

A Sourcegraph extension that adds Git blame and other useful features to code views on Sourcegraph, GitHub, GitLab, etc.
TypeScript
18
star
80

xconf

xconf.io is a config file search engine built on the Sourcegraph (https://sourcegraph.com) API. It currently supports Dockerfiles.
Go
18
star
81

go-jsonschema

EXPERIMENTAL: A Go library for working with JSON Schema (draft-07): parsing schemas, generating Go types from a JSON Schema
Go
17
star
82

themes

Color themes for Sourcegraph and editors
Shell
17
star
83

talks

Talks given about Sourcegraph or by Sourcegraphers
Go
17
star
84

phabricator-extension

Get code intelligence on Phabricator
PHP
16
star
85

httpfstream

httpstream provides HTTP handlers for simultaneous streaming uploads and downloads of objects, as well as persistence and a standalone server.
Go
16
star
86

batch-change-examples

A collection of examples for Batch Changes
Python
16
star
87

sourcegraph-alfred

Sourcegraph workflow for Alfred
Python
15
star
88

gen-mocks

Go
14
star
89

sbt-sourcegraph

sbt plugin to upload LSIF indexes to Sourcegraph for precise code intelligence
Scala
13
star
90

go-vcsurl

Lenient VCS repository URL parsing library for Go
Go
13
star
91

srclib-javascript

JavaScript (node.js) toolchain for srclib
JavaScript
12
star
92

appmon

Appmon tracks API calls in Web applications that use Go.
Go
12
star
93

srclib-php

PHP toolchain for srclib (https://srclib.org) - WORK IN PROGRESS
PHP
12
star
94

yj

Convert YAML to JSON
Go
12
star
95

go-papertrail

Go API client for papertrail (https://papertrailapp.com), a hosted log management service.
Go
12
star
96

log

OpenTelemetry-compatible Zap logger for Sourcegraph
Go
11
star
97

scip-go

SCIP indexer for Golang
Go
11
star
98

annotate

Go package for applying multiple sets of annotations to a region of text
Go
11
star
99

codesearchguide.org

Everything you ever wanted to know about code search. (WIP, will be online soon!)
CSS
11
star
100

multicache

Package multicache provides a "fallback" cache implementation that short-circuits gets and writes/deletes to all underlying caches.
Go
11
star