state-threads, a coroutine library for C network servers
Lightweight thread library for C/C++ coroutine (similar to goroutine), for high performance network servers.
At a glance
- What is it?
- ossrs/state-threads is a small C library that gives you stackful coroutines in the style of goroutines, used as the concurrency layer under the SRS media server. It is a fork of the SourceForge project patched for SRS, still built with a plain Makefile, and it needs valgrind-friendly flags to be debuggable at all.
- Who is it for?
- Adopt state-threads if you are writing a C or C++ network server and want one coroutine per connection without an event loop or an OS thread per client. Skip it if you are starting a new service, since the library's own release history stops in 2022 and the maintained version lives as a patch set inside SRS rather than as an independent project.
- Can I use it commercially?
- Check first. The repository uses a licence we do not classify automatically, so read its LICENSE file before any commercial use.
- Is it still maintained?
- Yes. The repository last received commits 12 days ago.
- What is it written in?
- Mainly C++, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.
Editorial analysis
What the library is and where it actually lives
state-threads is described as a lightweight thread library for C and C++ coroutines, similar to goroutines, for high performance network servers. The README is precise about its provenance: it is a fork of the project at sourceforge.net/projects/state-threads, patched for SRS, the streaming server whose repository is ossrs/srs.
That relationship is the single most important fact about the project. The branch you want is srs, and the README says explicitly that for the original state-threads without any changes you should check out the ST master branch. The srs branch was patched and refined, with the changes recorded in a CHANGELOG.md in the repository.
The consequence for an adopter is that the library with a public release history is not the library the README documents. The listed releases are v1.9.3 in October 2021, v1.9.4 in September 2022 and v1.9.5 in November 2022, while the Makefile at the root carries VERSION = 1.9.3 for the full library. So the tagged releases come from the upstream line, and the maintained work is the patch set that SRS applies.
The mechanism is stackful coroutines. Each coroutine has its own stack, and a context switch is a save and restore of the machine state. That is a different design from the event loop that most modern servers use, and the difference in cost profile is the difference between a stack switch and a callback dispatch.
The licence is dual, MPL or GPLv2, which the README links to the state-threads entry in the SRS licence page. The repository's own licence field reports it as a custom licence GitHub cannot classify, and the Makefile header records the Mozilla Public Licence version 1.1 with the GPL as an alternative, along with the history of the original code from the Netscape Portable Runtime library and contributions from Silicon Graphics.
The default branch is srs and the last push was on 2026-10-11, so the patch set is still being touched even though the tagged releases stopped.
Building it on Linux, macOS and Windows
The build is a plain Makefile with targets per platform, and the README gives the exact invocations for each. On Linux you clone the srs branch and build the debug library:
git clone -b srs https://github.com/ossrs/state-threads.git
make linux-debugOn macOS the target is darwin-debug, and on Windows there are win64-debug and win64-optimized targets that produce the static library obj/libst.a.
The compiler flags are passed through an EXTRA_CFLAGS variable, and that is where the interesting configuration lives. For Linux aarch64, which fails with an Unknown CPU architecture error, you add -D__aarch64__, and the README links issue 22 for other architectures. For valgrind support on Linux you add -DMD_VALGRIND, and for epoll as well you add -DMD_HAVE_EPOLL -DMD_VALGRIND. For AddressSanitizer on Linux it is -DMD_ASAN, and on Windows -DMD_ASAN -fsanitize=address, with a note that cl has no -fno-omit-frame-pointer.
The macOS build needs the kqueue flag for the same event mechanism Linux gets from epoll, so -DMD_HAVE_KQUEUE -DMD_VALGRIND -I/usr/local/include is the documented incantation.
The Windows story is the most unusual part of the README and worth reading in full. It is a native x64 build with the Visual Studio toolchain, using cl, ml64 and lib through GNU make. There is no Cygwin, no MinGW, no WSL and no POSIX emulation layer: GNU make and Git Bash only run the build. You need Visual Studio 2022 or its Build Tools with the MSVC x64 tools, Git for Windows for Git Bash, and GNU make. You open the x64 Native Tools Command Prompt so that vcvars64.bat puts the compiler on PATH, then start Git Bash inside it. The library links ws2_32.lib itself, and programs that include st.h must include it before windows.h or define WIN32_LEAN_AND_MEAN, with a porting notes page at docs/notes.html for the differences.
Cygwin64 is marked deprecated in the README with a note not to use it.
Debugging, valgrind and the GDB tooling
The README puts a lot of effort into debugging, which tells you what kind of library this is.
The GDB tools section lists two additions, both marked as supported with links to the discussion. The first shows the number of coroutines in a session, and the second shows all the coroutines along with the caller function. For a library whose whole point is that the stack of execution is not the thread stack, being able to see what coroutines exist and who started them is not a convenience but a requirement.
The valgrind section is a manual. It links the valgrind manual for debugging with gdb under valgrind and the valgrind command line documentation for startup parameters, then lists the options that matter for this kind of code.
The first is --undef-value-errors, which controls whether Memcheck reports uses of undefined values, and the README notes that setting it to no also speeds Memcheck up somewhat. The second is --leak-check, with the values no, summary, yes and full, where summary reports how many leaks occurred and full shows each one in detail. The third is --track-origins, which controls whether Memcheck tracks where an uninitialised value came from, and the README explains why it matters: by default Memcheck can tell you that an uninitialised value is being used dangerously but not where it came from, which makes tracking down the root problem difficult.
The last two are --show-reachable and --show-possibly-lost, to show the memory in use.
There is also a unit test story. The tests use Google Test in the utest/gtest-fit directory, and the Linux target is make linux-debug-utest followed by running ./obj/st_utest, with darwin-debug-utest and win64-debug-utest for the other platforms. The README warns that the gcc 4.8 on CentOS is too old and gives a docker run command using the ossrs/srs:dev-gcc7 image to run the suite instead. On Windows the tests that need POSIX features such as fork, signals, pipes and files are not built, and a few are skipped with a reason, for example the write tests that need a partial send, which Winsock never does.
For Visual Studio debugging there is a CMake path that generates a solution:
cmake -S cmake -B C:\st-build -G "Visual Studio 17 2022" -A x64
cmake --build C:\st-build --config Debug --target st_utestThe source layout and what each piece does
The repository top level shows the implementation, and the file names describe the internals more precisely than the README does.
The public interface is public.h, with st.pc.in providing the pkg-config metadata and st.spec for packaging. The scheduler, which is the heart of the library, is sched.c. The per-thread state lives in stk.c, the coroutine stack management, and the context switching primitives are in md.h together with the platform assembly: md_linux.S, md_darwin.S, md_cygwin64.S and md_win64.asm. The remaining sources are common.c and common.h for shared helpers, io.c for the I/O polling layer, sync.c for synchronisation primitives, key.c for the key-value data structure used by the timer wheel, event.c for the event loop, and the shell support.
The ide/ directory holds the GDB integration, which is where the coroutine listing commands live, and the tools/ directory holds the gdb scripts. The utest/ directory holds the unit tests with the bundled Google Test, the auto/ directory holds the code generation scripts, the docs/ directory holds the documentation including the Windows porting notes, and the cmake/ directory holds the CMake configuration used for the Visual Studio solution and for the tests.
Two more files are worth a glance. libst.def is the export definition for the Windows DLL, and run_and_diff.cmake is the script that runs the test suite and compares the output against the expected results in result/, which is how the conformance tests detect behaviour changes rather than just crashes.
The osguess.sh script at the root is a small utility for working out which platform macros to define, which is the kind of thing that saves time when you are porting the library yourself rather than using the Makefile targets.
Where it fits against the alternatives
The alternative every C network server reaches for first is an event loop, and the difference in approach is the point of this library.
An event loop with non-blocking I/O and callbacks or a readiness poller avoids context switches entirely. You pay for that with a callback per operation, state machines spread across the code, and the difficulty of reasoning about a request that spans several asynchronous steps. A stackful coroutine gives you the opposite: write the handler as if it were blocking, with ordinary sequential code, and let the library switch stacks when the handler waits.
The cost is a context switch per blocking operation, which is far more expensive than a poll event, and a stack per coroutine, which costs memory. For a server with a very large number of mostly-idle connections, an event loop usually wins. For a server with a moderate number of connections and handlers that are easier to write straight-line, coroutines often win, and that is the trade state-threads is making.
The other alternatives are the ones the ecosystem has since moved to. C++20 brought std::coroutine to the language, which are stackless rather than stackful, so they do not remove this library's niche but they do provide a standard mechanism with a different profile. Boost has its own coroutine library with a similar stackful approach. And for C specifically there is no standard answer, which is why libraries like this, and libaco, and libfib exist.
What you get here that you would not get elsewhere is a small, dependency-free implementation with a long history, and a maintained fork with a real user in the shape of SRS. What you give up is an independent release cadence for the version you would actually use, since the tagged releases stop in 2022 and the maintained code lives in a branch whose changes are documented in a changelog rather than in a versioned series.
If you are starting a new server today, that is a significant consideration. If you are maintaining a C server that already has a scheduler abstraction, or you want to port something away from a thread-per-connection model, the case is different.
Editorial conclusion
Adopt state-threads if you are writing a C or C++ network server and want one coroutine per connection without an event loop or an OS thread per client. Skip it if you are starting a new service, since the library's own release history stops in 2022 and the maintained version lives as a patch set inside SRS rather than as an independent project. Verify first by building the debug target and running the unit tests, then by checking the scheduler behaviour under load with your own connection pattern, because the context switch cost and the timer resolution are the two things that decide whether it fits.
Frequently asked questions
What is state-threads and what is it used for?
It is a lightweight C library providing stackful coroutines in the style of goroutines, intended for high performance network servers where one coroutine per connection is preferable to a thread per connection or a callback-based event loop.
Which branch of ossrs/state-threads should I use?
The srs branch, which is the fork patched and refined for the SRS server. The README says to check out the master branch only for the original state-threads without any changes. The listed tagged releases stop at v1.9.5 in November 2022.
How do I build state-threads on Linux and macOS?
Clone the srs branch, then run make linux-debug on Linux or make darwin-debug on macOS. Configuration flags are passed through EXTRA_CFLAGS, for example -D__aarch64__ for aarch64 Linux, -DMD_VALGRIND for valgrind support and -DMD_ASAN for AddressSanitizer.
Does state-threads build on Windows?
Yes, natively on x64 with the Visual Studio toolchain through GNU make, producing the static library obj/libst.a. There is no Cygwin, MinGW, WSL or POSIX emulation layer, and Cygwin64 is marked deprecated. You need Visual Studio 2022 or its Build Tools, Git for Windows and GNU make.
How do I run the state-threads unit tests?
Run make linux-debug-utest and then ./obj/st_utest, or the darwin-debug-utest and win64-debug-utest equivalents. The tests use Google Test from utest/gtest-fit, and the README notes that the gcc 4.8 on CentOS is too old, so it gives a docker run command using the ossrs/srs:dev-gcc7 image.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/ossrs-state-threads)Was this useful?