{"id":13643576,"url":"https://github.com/talostrading/sonic","last_synced_at":"2025-04-21T02:30:40.248Z","repository":{"id":236210794,"uuid":"490232551","full_name":"talostrading/sonic","owner":"talostrading","description":"Sonic is a Go library for network and I/O programming that provides developers with a consistent asynchronous model, with a focus on achieving the lowest possible latency and jitter in Go.","archived":false,"fork":false,"pushed_at":"2025-03-10T15:09:48.000Z","size":3981,"stargazers_count":724,"open_issues_count":24,"forks_count":21,"subscribers_count":17,"default_branch":"master","last_synced_at":"2025-03-10T16:36:40.652Z","etag":null,"topics":[],"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/talostrading.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":"CODEOWNERS","security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-05-09T10:17:05.000Z","updated_at":"2025-03-10T15:09:52.000Z","dependencies_parsed_at":"2024-04-26T05:27:50.184Z","dependency_job_id":"38bf4e21-43af-43f4-a76f-e91d7296617b","html_url":"https://github.com/talostrading/sonic","commit_stats":null,"previous_names":["talostrading/sonic"],"tags_count":88,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/talostrading%2Fsonic","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/talostrading%2Fsonic/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/talostrading%2Fsonic/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/talostrading%2Fsonic/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/talostrading","download_url":"https://codeload.github.com/talostrading/sonic/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":249986028,"owners_count":21356310,"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":["production-code"],"created_at":"2024-08-02T01:01:49.529Z","updated_at":"2025-04-21T02:30:39.289Z","avatar_url":"https://github.com/talostrading.png","language":"Go","funding_links":[],"categories":["Go"],"sub_categories":[],"readme":"*work-in-progress - expect breaking changes until v1.0.0*\n\n# Sonic\n\nSonic is a Go library for network and I/O programming that provides developers with a consistent asynchronous model,\nwith a focus on achieving the lowest possible latency and jitter in Go. Sonic aims to make it easy to write network\nprotocols (websocket, http2, custom exchange binary) on a series of bytestreams and then make use of those bytestreams\nthrough multiple connections running in a **single-thread and goroutine**.\n\nSonic is an alternative to the `net` package. It removes the need to use multiple goroutines to handle\nmultiple connections and reads/writes in the same process. By doing that, a single goroutine\nand thread is used to read/write from multiple connections which brings several benefits:\n\n- No need to use synchronization primitives (channels, mutexes, etc.) as multiple connections can be handled in the same\n  goroutine.\n- It removes the need for the Go scheduler to do any work which could slow down the program.\n- It allows latency-sensitive programs to run in a hot-loop pinned to a thread on an isolated core in order to achieve\n  low latency and jitter.\n\nSonic currently supports only Unix-based systems (BSD, macOS, Linux).\n\n```go\nfunc main() {\n    // Create an IO object which can execute asynchronous operations on the\n    // current goroutine.\n    ioc := sonic.MustIO()\n    defer ioc.Close()\n\n    // Create 10 connections. Each connection reads a message into it's\n    // buffer and then closes.\n    for i := 0; i \u003c 10; i++ {\n        conn, _ := sonic.Dial(ioc, \"tcp\", \"localhost:8080\")\n\t\t\n        b := make([]byte, 128)\n        conn.AsyncRead(b, func(err error, n int) {\n            if err != nil {\n                fmt.Printf(\"could not read from %d err=%v\\n\", i, err)\n            } else {\n                b = b[:n]\n                fmt.Println(\"got=\", string(b))\n                conn.Close()\n            }\n        })\n    }\n\n    // Execute all pending reads scheduled in the for-loop, then exit.\n    ioc.RunPending()\n}\n```\n\n## Getting Started\n\nSee `examples/`. A good starting point is `examples/timer`. All examples can be built by calling `make` in the root path\nof sonic. The builds will be put in `bin/`.\n\n### UDP Multicast\n\n`sonic` offers a full-featured `UDP Multicast` peer for both `IPv4` and `IPv6`. See `multicast/peer.go`. This peer can\nread and write data to a multicast group, join a group with source-IP and network interface filtering, and control its\ngroup membership by blocking/unblocking source-IPs at runtime.\n\nMoreover, this peer, unlike the `websocket` client, does not allocate and copy any data in any of its functions.\nAdditionally, the peer gives the programmer the option to change its read buffer after scheduling a read on it i.e.\n```go\nvar (\n    b1, b2 []byte\n)\npeer.AsyncRead(b1, func(...) { ... }) // schedule an asynchronous read in b1\n// ... some other code here\npeer.SetAsyncReadBuffer(b2) // make the previously scheduled asynchronous read use b2 instead of b1\n```\n\nThis is very useful when multiple `UDP` peers share the same read buffer. For example:\n\n```go\nb := make([]byte, 1024 * 1024)\n\n// We expect packets to be less than 256 bytes. When either peer reads, it calls the updateAndProcessBuffer function.\npeer1.AsyncRead(b[:256], func(...) { updateAndProcessBuffer() })\npeer2.AsyncRead(b[:256], func(...) { updateAndProcessBuffer() })\n\nfunc updateAndProcessBuffer() {\n    // One of the peers read something. We instruct the peers to read into the next 256 byte chunk of b such that we can\n    // process the previous 256 bytes.\n    peer1.AsyncRead(b[256:512], func(...) { updateAndProcessBuffer() })\n    peer2.AsyncRead(b[256:512], func(...) { updateAndProcessBuffer() })\n\n    go process(b[:256])\n}\n```\n\n### Zero-copy FIFO buffers\n\nWe provide two types of FIFO buffers with zero-copy semantics. Regardless of the type, a FIFO buffer is essential when\nwriting protocol encoders/decoders over UDP or TCP with Linux's socket API to minimize syscalls. For example,\nsay we have a simple protocol where each message has a fixed-size header and a variable-sized payload - the length of\nthe payload is in the header. Say we read data through TCP. We then have two options:\n```go\n// buffer in which we read; assume header size is 1 byte.\n\nb := make([]byte, 1024)\n\n// option 1: read the header first and then the payload from the network\nconn.Read(b[:1]) // read the header\npayloadSize := int(b[0])\npayload := b[1:payloadSize]\nconn.Read(payload) // read the payload\n// do something with the payload\n\n// option 2: read as much as you can from the network and then parse the bytes\nconn.Read(b)\ni := 0\nwhile i \u003c len(b) {\n    payloadSize := int(b[i:i+1])\n    if i + 1 + payloadSize \u003c= len(b) {\n        payload := b[i+1:i+1+payloadSize]\n        process(payload)\n    }\n    i += 1 + payloadSize\n}\n\n```\n\n`option 1` is not efficient as `n` messages need `n * 2` syscalls. `option 2` is efficient as the number if syscalls is\nminimized - in the limit, we need just 1 syscall to read `n` messages. `option 2` however is missing something:\n\n- what if the last read message was incomplete i.e. we read the header with its size, say `255`, but only had space to\n  read `100` of those bytes into `b` as we're near the end of `b`.\n- to read the rest of the `255 - 100 = 155` bytes of the payload, we need to move the read `100` bytes to the beginning\n  of `b`, overwriting the already processed payloads.\n- in other words, we need FIFO semantics over `b`.\n\nThe naive way of offering FIFO semantics over b would be to simply copy the `100` bytes to the beginning of the slice.\nBut that's a `memcpy` that will take a lot of time if the message is relatively big, say over 1KB. That's not\nacceptable even though that's how we do things for websocket (see `byte_buffer.go` and `codec/websocket/codec.go`).\nIn those cases we offer two types of FIFO semantics over a byte slice, both offering the same API:\n\n- `Claim(n) []byte` - claim at most n bytes from the underlying `[]byte` slice. Callers can now read into the returned\n  slice.\n- `Commit(n) int` - commit at most n previously claimed bytes i.e. queue at most `n` bytes\n- `Consume(n) int` - consume at most n previously committed/queued bytes\n\n#### Mirrored Buffer\n\nThis is a zero-copy FIFO buffer that works for both TCP and UDP protocols. It offers contiguous byte slices in a FIFO\nmanner without care for wrapping. See `bytes/mirrored_buffer.go`. The only limitations are that the buffer size must be\na multiple of the system's page size and the system must expose a shared memory filesystem like `/dev/shm`. In short, the\nmirrored buffer provides zero-copy FIFO semantics over a byte slice in the following way:\n\n- it creates the underlying byte slice of size `n` (where `n` is a multiple of page size) and **maps it twice,\n  contiguously, in the process' virtual address space** with `mmap`.\n- there are `n` physical bytes backing up the underlying byte slice and `n * 2` virtual bytes\n- the buffer is mirrored in a sense that reading/writing to the sequence `b[n], b[n+1], ..., b[2*n-1]` is permitted and\n  in fact, touches the bytes at `b[0], b[1], ..., b[n-1]`\n\n#### Bip Buffer\n\nThis is a zero-copy FIFO buffer meant solely for writing packet-based (UDP) protocols. Refer to the\ncreator's [post](https://www.codeproject.com/Articles/3479/The-Bip-Buffer-The-Circular-Buffer-with-a-Twist) for an\nexplanation of how it works.\n\n#### What is next\n\nThe two buffers above are not yet standardized across sonic. TCP codecs, including `websocket`, still use the `memcpy`\nbased byte buffer abstraction `byte_buffer.go` which is not that performant for large messages. The plan is to port\nwebsocket to use the mirrored buffer by `v1.0.0`.\n\nThe Bip Buffer is actively used in Talos UDP-based gateways.\n\n## Peculiarities\n\n### Async preemption\n\nIf, for some reason, you have a single goroutine that ends up waiting for more than 10ms for something to happen, sonic\nwill crash on Linux due to `epoll_wait` being interrupted by the signal SIGURG. This happens because, by default, the Go\nruntime non-cooperatively preempts goroutines that are idle for more than 10ms. To turn off this behavior,\nset `GODEBUG=asyncpreemptoff=1` before running your binary.\n\nThis issue has been addressed\nin [this](https://github.com/talostrading/sonic/commit/d59145deb86647460abd9e85eddbdb03f50e2b01) commit.\n\n### Credits\n\n- [boost.asio](https://www.boost.org/doc/libs/1_75_0/doc/html/boost_asio.html) - the main inspiration for the sonic API\n- [boost.beast](https://github.com/boostorg/beast)\n- [mio](https://github.com/tokio-rs/mio)\n- [tungstenite-rs](https://github.com/snapview/tungstenite-rs)\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"https://c.tenor.com/OTDlqAguqpEAAAAi/sonic-running.gif\" /\u003e\n\u003c/p\u003e\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftalostrading%2Fsonic","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftalostrading%2Fsonic","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftalostrading%2Fsonic/lists"}