scheduler.c

Basic task scheduling library
git clone git://git.finwo.net/lib/scheduler.c
Log | Files | Refs | README | LICENSE

commit 5b94db2be33be124cbfbb9bdac123f2690f89768
parent 84c5daa19aa76209ed633581abc1f3c591ee1d8f
Author: finwo <finwo@pm.me>
Date:   Wed, 18 Mar 2026 20:46:10 +0100

Add semaphores

Diffstat:
A.clang-format | 334+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
M.dep.export | 1+
MREADME.md | 77+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mconfig.mk | 1+
Mexamples/.gitignore | 1+
Mexamples/Makefile | 5++++-
Mexamples/example_basic.c | 16+++++++++-------
Mexamples/example_io.c | 125++++++++++++++++++++++++++++++++++++++++---------------------------------------
Mexamples/example_multi.c | 151++++++++++++++++++++++++++++++++++++++++---------------------------------------
Mexamples/example_periodic.c | 47++++++++++++++++++++++++-----------------------
Mexamples/example_removal.c | 83++++++++++++++++++++++++++++++++++++++++---------------------------------------
Aexamples/example_sem.c | 57+++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/sched-sem.c | 13+++++++++++++
Asrc/sched-sem.h | 17+++++++++++++++++
14 files changed, 719 insertions(+), 209 deletions(-)

diff --git a/.clang-format b/.clang-format @@ -0,0 +1,334 @@ +--- +Language: Cpp +AccessModifierOffset: -1 +AlignAfterOpenBracket: Align +AlignArrayOfStructures: None +AlignConsecutiveAssignments: + Enabled: true + AcrossEmptyLines: false + AcrossComments: false + AlignCompound: false + AlignFunctionDeclarations: false + AlignFunctionPointers: false + PadOperators: true +AlignConsecutiveBitFields: + Enabled: false + AcrossEmptyLines: false + AcrossComments: false + AlignCompound: false + AlignFunctionDeclarations: false + AlignFunctionPointers: false + PadOperators: false +AlignConsecutiveDeclarations: + Enabled: true + AcrossEmptyLines: false + AcrossComments: false + AlignCompound: false + AlignFunctionDeclarations: true + AlignFunctionPointers: false + PadOperators: false +AlignConsecutiveMacros: + Enabled: true + AcrossEmptyLines: false + AcrossComments: false + AlignCompound: false + AlignFunctionDeclarations: false + AlignFunctionPointers: false + PadOperators: false +AlignConsecutiveShortCaseStatements: + Enabled: true + AcrossEmptyLines: false + AcrossComments: false + AlignCaseArrows: false + AlignCaseColons: false +AlignConsecutiveTableGenBreakingDAGArgColons: + Enabled: false + AcrossEmptyLines: false + AcrossComments: false + AlignCompound: false + AlignFunctionDeclarations: false + AlignFunctionPointers: false + PadOperators: false +AlignConsecutiveTableGenCondOperatorColons: + Enabled: false + AcrossEmptyLines: false + AcrossComments: false + AlignCompound: false + AlignFunctionDeclarations: false + AlignFunctionPointers: false + PadOperators: false +AlignConsecutiveTableGenDefinitionColons: + Enabled: false + AcrossEmptyLines: false + AcrossComments: false + AlignCompound: false + AlignFunctionDeclarations: false + AlignFunctionPointers: false + PadOperators: false +AlignEscapedNewlines: Left +AlignOperands: Align +AlignTrailingComments: + Kind: Always + OverEmptyLines: 0 +AllowAllArgumentsOnNextLine: true +AllowAllParametersOfDeclarationOnNextLine: true +AllowBreakBeforeNoexceptSpecifier: Never +AllowShortBlocksOnASingleLine: Never +AllowShortCaseExpressionOnASingleLine: true +AllowShortCaseLabelsOnASingleLine: false +AllowShortCompoundRequirementOnASingleLine: true +AllowShortEnumsOnASingleLine: true +AllowShortFunctionsOnASingleLine: None +AllowShortIfStatementsOnASingleLine: WithoutElse +AllowShortLambdasOnASingleLine: All +AllowShortLoopsOnASingleLine: true +AllowShortNamespacesOnASingleLine: false +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakBeforeMultilineStrings: true +AttributeMacros: + - __capability + - absl_nonnull + - absl_nullable + - absl_nullability_unknown +BinPackArguments: true +BinPackLongBracedList: true +BinPackParameters: BinPack +BitFieldColonSpacing: Both +BracedInitializerIndentWidth: -1 +BraceWrapping: + AfterCaseLabel: false + AfterClass: false + AfterControlStatement: Never + AfterEnum: false + AfterExternBlock: false + AfterFunction: true + AfterNamespace: false + AfterObjCDeclaration: false + AfterStruct: false + AfterUnion: false + BeforeCatch: false + BeforeElse: false + BeforeLambdaBody: false + BeforeWhile: false + IndentBraces: false + SplitEmptyFunction: true + SplitEmptyRecord: true + SplitEmptyNamespace: true +BreakAdjacentStringLiterals: true +BreakAfterAttributes: Leave +BreakAfterJavaFieldAnnotations: false +BreakAfterReturnType: None +BreakArrays: true +BreakBeforeBinaryOperators: None +BreakBeforeConceptDeclarations: Always +BreakBeforeBraces: Attach +BreakBeforeInlineASMColon: OnlyMultiline +BreakBeforeTemplateCloser: false +BreakBeforeTernaryOperators: true +BreakBinaryOperations: Never +BreakConstructorInitializers: BeforeColon +BreakFunctionDefinitionParameters: false +BreakInheritanceList: BeforeColon +BreakStringLiterals: true +BreakTemplateDeclarations: Yes +ColumnLimit: 120 +CommentPragmas: '^ IWYU pragma:' +CompactNamespaces: false +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: true +DerivePointerAlignment: false +DisableFormat: false +EmptyLineAfterAccessModifier: Never +EmptyLineBeforeAccessModifier: LogicalBlock +EnumTrailingComma: Leave +ExperimentalAutoDetectBinPacking: false +FixNamespaceComments: true +ForEachMacros: + - foreach + - Q_FOREACH + - BOOST_FOREACH +IfMacros: + - KJ_IF_MAYBE +IncludeBlocks: Regroup +IncludeCategories: + - Regex: '^<ext/.*\.h>' + Priority: 2 + SortPriority: 0 + CaseSensitive: false + - Regex: '^<.*\.h>' + Priority: 1 + SortPriority: 0 + CaseSensitive: false + - Regex: '^<.*' + Priority: 2 + SortPriority: 0 + CaseSensitive: false + - Regex: '.*' + Priority: 3 + SortPriority: 0 + CaseSensitive: false +IncludeIsMainRegex: '([-_](test|unittest))?$' +IncludeIsMainSourceRegex: '' +IndentAccessModifiers: false +IndentCaseBlocks: true +IndentCaseLabels: true +IndentExportBlock: true +IndentExternBlock: AfterExternBlock +IndentGotoLabels: true +IndentPPDirectives: None +IndentRequiresClause: true +IndentWidth: 2 +IndentWrappedFunctionNames: false +InsertBraces: false +InsertNewlineAtEOF: true +InsertTrailingCommas: None +IntegerLiteralSeparator: + Binary: 0 + BinaryMinDigits: 0 + Decimal: 0 + DecimalMinDigits: 0 + Hex: 0 + HexMinDigits: 0 +JavaScriptQuotes: Leave +JavaScriptWrapImports: true +KeepEmptyLines: + AtEndOfFile: false + AtStartOfBlock: false + AtStartOfFile: false +KeepFormFeed: false +LambdaBodyIndentation: Signature +LineEnding: DeriveLF +MacroBlockBegin: '' +MacroBlockEnd: '' +MainIncludeChar: Quote +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +ObjCBinPackProtocolList: Never +ObjCBlockIndentWidth: 2 +ObjCBreakBeforeNestedBlockParam: true +ObjCSpaceAfterProperty: false +ObjCSpaceBeforeProtocolList: true +OneLineFormatOffRegex: '' +PackConstructorInitializers: NextLine +PenaltyBreakAssignment: 2 +PenaltyBreakBeforeFirstCallParameter: 1 +PenaltyBreakBeforeMemberAccess: 150 +PenaltyBreakComment: 300 +PenaltyBreakFirstLessLess: 120 +PenaltyBreakOpenParenthesis: 0 +PenaltyBreakScopeResolution: 500 +PenaltyBreakString: 1000 +PenaltyBreakTemplateDeclaration: 10 +PenaltyExcessCharacter: 1000000 +PenaltyIndentedWhitespace: 0 +PenaltyReturnTypeOnItsOwnLine: 200 +PointerAlignment: Right +PPIndentWidth: -1 +QualifierAlignment: Leave +RawStringFormats: + - Language: Cpp + Delimiters: + - cc + - CC + - cpp + - Cpp + - CPP + - 'c++' + - 'C++' + CanonicalDelimiter: '' + BasedOnStyle: google + - Language: TextProto + Delimiters: + - pb + - PB + - proto + - PROTO + EnclosingFunctions: + - EqualsProto + - EquivToProto + - PARSE_PARTIAL_TEXT_PROTO + - PARSE_TEST_PROTO + - PARSE_TEXT_PROTO + - ParseTextOrDie + - ParseTextProtoOrDie + - ParseTestProto + - ParsePartialTestProto + CanonicalDelimiter: pb + BasedOnStyle: google +ReferenceAlignment: Pointer +ReflowComments: Always +RemoveBracesLLVM: false +RemoveEmptyLinesInUnwrappedLines: false +RemoveParentheses: Leave +RemoveSemicolon: false +RequiresClausePosition: OwnLine +RequiresExpressionIndentation: OuterScope +SeparateDefinitionBlocks: Leave +ShortNamespaceLines: 1 +SkipMacroDefinitionBody: false +SortIncludes: + Enabled: true + IgnoreCase: false +SortJavaStaticImport: Before +SortUsingDeclarations: LexicographicNumeric +SpaceAfterCStyleCast: false +SpaceAfterLogicalNot: false +SpaceAfterOperatorKeyword: false +SpaceAfterTemplateKeyword: true +SpaceAroundPointerQualifiers: Default +SpaceBeforeAssignmentOperators: true +SpaceBeforeCaseColon: false +SpaceBeforeCpp11BracedList: false +SpaceBeforeCtorInitializerColon: true +SpaceBeforeInheritanceColon: true +SpaceBeforeJsonColon: false +SpaceBeforeParens: ControlStatements +SpaceBeforeParensOptions: + AfterControlStatements: true + AfterForeachMacros: true + AfterFunctionDefinitionName: false + AfterFunctionDeclarationName: false + AfterIfMacros: true + AfterNot: false + AfterOverloadedOperator: false + AfterPlacementOperator: true + AfterRequiresInClause: false + AfterRequiresInExpression: false + BeforeNonEmptyParentheses: false +SpaceBeforeRangeBasedForLoopColon: true +SpaceBeforeSquareBrackets: false +SpaceInEmptyBlock: false +SpacesBeforeTrailingComments: 2 +SpacesInAngles: Never +SpacesInContainerLiterals: true +SpacesInLineCommentPrefix: + Minimum: 1 + Maximum: -1 +SpacesInParens: Never +SpacesInParensOptions: + ExceptDoubleParentheses: false + InCStyleCasts: false + InConditionalStatements: false + InEmptyParentheses: false + Other: false +SpacesInSquareBrackets: false +Standard: Auto +StatementAttributeLikeMacros: + - Q_EMIT +StatementMacros: + - Q_UNUSED + - QT_REQUIRE_VERSION +TableGenBreakInsideDAGArg: DontBreak +TabWidth: 8 +UseTab: Never +VerilogBreakBetweenInstancePorts: true +WhitespaceSensitiveMacros: + - BOOST_PP_STRINGIZE + - CF_SWIFT_NAME + - NS_SWIFT_NAME + - PP_STRINGIZE + - STRINGIZE +WrapNamespaceBodyWithEmptyLines: Leave +... + diff --git a/.dep.export b/.dep.export @@ -1 +1,2 @@ include/finwo/scheduler.h src/scheduler.h +include/finwo/sched-sem.h src/sched-sem.h diff --git a/README.md b/README.md @@ -33,6 +33,7 @@ gcc -o greet examples/example_basic.c src/scheduler.c && ./greet - **Minimal** — ~120 lines of C, single header + single source file - **No dependencies** — only `<stdint.h>` and `<sys/select.h>` - **I/O multiplexing** — built-in `select(2)` integration via `sched_has_data()` +- **Semaphores** — cooperative synchronization via `sched_sem_*()` primitives - **Cross-project friendly** — zero-configuration include via `config.mk` - **Extensible** — task `udata` lets you attach arbitrary state @@ -167,6 +168,80 @@ int ready = sched_has_data(fds); // returns first ready FD, or -1 if none - **Returns** — the first ready FD from the array, or `-1` if none are ready +### Semaphores + +Semaphores provide a lightweight synchronization primitive for coordinating tasks. The `value` field can represent signal counts, available bytes, or any resource count you choose. + +```c +#include "src/sched-sem.h" + +struct sched_sem { + int value; +}; +``` + +> [!NOTE] +> Semaphores are allocated by the user on the stack or heap. The scheduler does not manage their lifetime. + +### `sched_sem_init` + +```c +void sched_sem_init(struct sched_sem *sem, int amount); +``` + +Set the semaphore to an initial value. + +```c +struct sched_sem mysem; +sched_sem_init(&mysem, 5); // Start with 5 available +``` + +### `sched_sem_signal` + +```c +void sched_sem_signal(struct sched_sem *sem, int amount); +``` + +Increment the semaphore by `amount`. Use to produce resources or signal events. + +```c +sched_sem_signal(&mysem, 1); // Add 1 to the count +``` + +### `sched_sem_consume` + +```c +void sched_sem_consume(struct sched_sem *sem, int amount); +``` + +Decrement the semaphore by `amount`. Use to consume resources. + +```c +sched_sem_consume(&mysem, 1); // Take 1 from the count +``` + +### `sched_sem_wait` + +```c +#define sched_sem_wait(sem) ... +``` + +A macro that blocks the current task if the semaphore is `<= 0`. If `value > 0`, the macro falls through and the task continues. If `value <= 0`, it returns `SCHED_RUNNING` to defer to the next tick. + +```c +static int worker(int64_t ts, pt_task_t *task) { + MyCtx *ctx = task->udata; + + sched_sem_wait(&ctx->sem); // Wait for availability + // If we get here, sem.value > 0 + + sched_sem_consume(&ctx->sem, 1); // Consume one unit + // ... do work ... + + return SCHED_RUNNING; +} +``` + --- ## Usage Examples @@ -180,6 +255,7 @@ Each example is a complete, compilable program. | [example_io.c](examples/example_io.c) | I/O monitoring with `sched_has_data()` | | [example_removal.c](examples/example_removal.c) | Parent removes child task via returned `pt_task_t*` | | [example_multi.c](examples/example_multi.c) | Multiple coordinated tasks sharing state | +| [example_sem.c](examples/example_sem.c) | Semaphore-based producer/consumer coordination | See [examples/README.md](examples/README.md) for build instructions. @@ -215,6 +291,7 @@ The scheduler uses `select(2)` rather than `poll(2)` or `epoll(7)`. This keeps t |---|---|---| | `sched_create()` → `udata` pointer | **Caller** | The scheduler never touches your `udata`. You must `free()` it yourself, typically in a cleanup task or after `sched_main()` returns. | | `sched_create()` → `pt_task_t` struct | **`sched_remove()`** | Freed automatically when the task returns `SCHED_DONE` or `SCHED_ERROR`, or when explicitly removed. | +| `sched_sem` struct | **Caller** | Semaphores are allocated by the user. The scheduler does not manage their lifetime. | **Pattern for owned `udata`:** diff --git a/config.mk b/config.mk @@ -1 +1,2 @@ SRC+={{module.dirname}}/src/scheduler.c +SRC+={{module.dirname}}/src/sched-sem.c diff --git a/examples/.gitignore b/examples/.gitignore @@ -3,4 +3,5 @@ /example_multi /example_periodic /example_removal +/example_sem /*.dSYM/ diff --git a/examples/Makefile b/examples/Makefile @@ -2,7 +2,7 @@ CC ?= gcc CFLAGS ?= -Wall -Wextra -g -I$(shell pwd)/.. LDFLAGS ?= -EXAMPLES = example_basic example_periodic example_io example_removal example_multi +EXAMPLES = example_basic example_periodic example_io example_removal example_multi example_sem .PHONY: all clean @@ -23,5 +23,8 @@ example_removal: example_removal.c ../src/scheduler.c ../src/scheduler.h example_multi: example_multi.c ../src/scheduler.c ../src/scheduler.h $(CC) $(CFLAGS) -o $@ $< ../src/scheduler.c $(LDFLAGS) +example_sem: example_sem.c ../src/scheduler.c ../src/sched-sem.c ../src/scheduler.h ../src/sched-sem.h + $(CC) $(CFLAGS) -o $@ $< ../src/scheduler.c ../src/sched-sem.c $(LDFLAGS) + clean: rm -f $(EXAMPLES) diff --git a/examples/example_basic.c b/examples/example_basic.c @@ -1,15 +1,17 @@ -#include "src/scheduler.h" #include <stdio.h> #include <stdlib.h> +#include "src/scheduler.h" + static int greet(int64_t ts, pt_task_t *task) { - (void)ts; (void)task; - printf("Hello, world!\n"); - return SCHED_DONE; + (void)ts; + (void)task; + printf("Hello, world!\n"); + return SCHED_DONE; } int main(void) { - sched_create(greet, NULL); - sched_main(); - return 0; + sched_create(greet, NULL); + sched_main(); + return 0; } diff --git a/examples/example_io.c b/examples/example_io.c @@ -1,4 +1,3 @@ -#include "src/scheduler.h" #include <errno.h> #include <fcntl.h> #include <stdio.h> @@ -8,87 +7,89 @@ #include <time.h> #include <unistd.h> +#include "src/scheduler.h" + typedef struct { - int pipe_fd; - int fds[2]; - int64_t last_write; + int pipe_fd; + int fds[2]; + int64_t last_write; } WriterCtx; typedef struct { - int pipe_fd; - int fds[2]; - int done; + int pipe_fd; + int fds[2]; + int done; } ReaderCtx; static int writer_task(int64_t ts, pt_task_t *task) { - static int count = 0; - WriterCtx *ctx = task->udata; - - if (ts - ctx->last_write >= 200) { - char msg[32]; - int len = snprintf(msg, sizeof(msg), "message #%d\n", ++count); - write(ctx->pipe_fd, msg, len); - ctx->last_write = ts; - - if (count >= 3) { - snprintf(msg, sizeof(msg), "quit\n"); - write(ctx->pipe_fd, msg, 4); - close(ctx->pipe_fd); - free(ctx); - return SCHED_DONE; - } + static int count = 0; + WriterCtx *ctx = task->udata; + + if (ts - ctx->last_write >= 200) { + char msg[32]; + int len = snprintf(msg, sizeof(msg), "message #%d\n", ++count); + write(ctx->pipe_fd, msg, len); + ctx->last_write = ts; + + if (count >= 3) { + snprintf(msg, sizeof(msg), "quit\n"); + write(ctx->pipe_fd, msg, 4); + close(ctx->pipe_fd); + free(ctx); + return SCHED_DONE; } + } - return SCHED_RUNNING; + return SCHED_RUNNING; } static int reader_task(int64_t ts, pt_task_t *task) { - (void)ts; - ReaderCtx *ctx = task->udata; - - int ready = sched_has_data(ctx->fds); - if (ready >= 0) { - char buf[256]; - ssize_t n = read(ready, buf, sizeof(buf) - 1); - if (n > 0) { - buf[n] = '\0'; - printf("[reader] %s", buf); - } - if (n == 0 || (n > 0 && strncmp(buf, "quit\n", 5) == 0)) { - ctx->done = 1; - } - } + (void)ts; + ReaderCtx *ctx = task->udata; - if (ctx->done) { - close(ctx->pipe_fd); - free(ctx); - return SCHED_DONE; + int ready = sched_has_data(ctx->fds); + if (ready >= 0) { + char buf[256]; + ssize_t n = read(ready, buf, sizeof(buf) - 1); + if (n > 0) { + buf[n] = '\0'; + printf("[reader] %s", buf); + } + if (n == 0 || (n > 0 && strncmp(buf, "quit\n", 5) == 0)) { + ctx->done = 1; } + } - return SCHED_RUNNING; + if (ctx->done) { + close(ctx->pipe_fd); + free(ctx); + return SCHED_DONE; + } + + return SCHED_RUNNING; } int main(void) { - int pipefd[2]; - if (pipe(pipefd) < 0) { - perror("pipe"); - return 1; - } + int pipefd[2]; + if (pipe(pipefd) < 0) { + perror("pipe"); + return 1; + } - WriterCtx *wctx = calloc(1, sizeof(WriterCtx)); - wctx->pipe_fd = pipefd[1]; + WriterCtx *wctx = calloc(1, sizeof(WriterCtx)); + wctx->pipe_fd = pipefd[1]; - ReaderCtx *rctx = calloc(1, sizeof(ReaderCtx)); - rctx->pipe_fd = pipefd[0]; - rctx->fds[0] = 1; - rctx->fds[1] = rctx->pipe_fd; + ReaderCtx *rctx = calloc(1, sizeof(ReaderCtx)); + rctx->pipe_fd = pipefd[0]; + rctx->fds[0] = 1; + rctx->fds[1] = rctx->pipe_fd; - struct timeval now; - gettimeofday(&now, NULL); - wctx->last_write = (int64_t)now.tv_sec * 1000 + now.tv_usec / 1000; + struct timeval now; + gettimeofday(&now, NULL); + wctx->last_write = (int64_t)now.tv_sec * 1000 + now.tv_usec / 1000; - sched_create(writer_task, wctx); - sched_create(reader_task, rctx); - sched_main(); - return 0; + sched_create(writer_task, wctx); + sched_create(reader_task, rctx); + sched_main(); + return 0; } diff --git a/examples/example_multi.c b/examples/example_multi.c @@ -1,4 +1,3 @@ -#include "src/scheduler.h" #include <fcntl.h> #include <stdio.h> #include <stdlib.h> @@ -6,100 +5,102 @@ #include <sys/time.h> #include <unistd.h> +#include "src/scheduler.h" + #define BUF_SIZE 64 typedef struct { - int reader_fd; - int writer_fd; - int fds_reader[2]; - char buf[BUF_SIZE]; - int done; - int64_t last_write; + int reader_fd; + int writer_fd; + int fds_reader[2]; + char buf[BUF_SIZE]; + int done; + int64_t last_write; } SharedCtx; static int reader_task(int64_t ts, pt_task_t *task) { - (void)ts; - SharedCtx *ctx = task->udata; - - int ready = sched_has_data(ctx->fds_reader); - if (ready >= 0) { - ssize_t n = read(ready, ctx->buf, BUF_SIZE - 1); - if (n > 0) { - ctx->buf[n] = '\0'; - printf("[reader] received: \"%s\"\n", ctx->buf); - } - if (n == 0 || (n > 0 && strncmp(ctx->buf, "done\n", 5) == 0)) { - ctx->done = 1; - } + (void)ts; + SharedCtx *ctx = task->udata; + + int ready = sched_has_data(ctx->fds_reader); + if (ready >= 0) { + ssize_t n = read(ready, ctx->buf, BUF_SIZE - 1); + if (n > 0) { + ctx->buf[n] = '\0'; + printf("[reader] received: \"%s\"\n", ctx->buf); } - - if (ctx->done) { - close(ctx->reader_fd); - ctx->reader_fd = -1; - return SCHED_DONE; + if (n == 0 || (n > 0 && strncmp(ctx->buf, "done\n", 5) == 0)) { + ctx->done = 1; } + } + + if (ctx->done) { + close(ctx->reader_fd); + ctx->reader_fd = -1; + return SCHED_DONE; + } - return SCHED_RUNNING; + return SCHED_RUNNING; } static int writer_task(int64_t ts, pt_task_t *task) { - SharedCtx *ctx = task->udata; - - if (ctx->done) { - return SCHED_DONE; + SharedCtx *ctx = task->udata; + + if (ctx->done) { + return SCHED_DONE; + } + + if (ts - ctx->last_write >= 200) { + static int count = 0; + char msg[32]; + int len = snprintf(msg, sizeof(msg), "message #%d\n", ++count); + write(ctx->writer_fd, msg, len); + ctx->last_write = ts; + + if (count >= 3) { + snprintf(msg, sizeof(msg), "done\n"); + write(ctx->writer_fd, msg, 4); + close(ctx->writer_fd); + ctx->writer_fd = -1; } + } - if (ts - ctx->last_write >= 200) { - static int count = 0; - char msg[32]; - int len = snprintf(msg, sizeof(msg), "message #%d\n", ++count); - write(ctx->writer_fd, msg, len); - ctx->last_write = ts; - - if (count >= 3) { - snprintf(msg, sizeof(msg), "done\n"); - write(ctx->writer_fd, msg, 4); - close(ctx->writer_fd); - ctx->writer_fd = -1; - } - } - - return SCHED_RUNNING; + return SCHED_RUNNING; } static int shutdown_task(int64_t ts, pt_task_t *task) { - (void)ts; - SharedCtx *ctx = task->udata; + (void)ts; + SharedCtx *ctx = task->udata; - if (ctx->done && ctx->reader_fd < 0 && ctx->writer_fd < 0) { - printf("[shutdown] all done, cleaning up.\n"); - free(ctx); - return SCHED_DONE; - } + if (ctx->done && ctx->reader_fd < 0 && ctx->writer_fd < 0) { + printf("[shutdown] all done, cleaning up.\n"); + free(ctx); + return SCHED_DONE; + } - return SCHED_RUNNING; + return SCHED_RUNNING; } int main(void) { - int pipefd[2]; - if (pipe(pipefd) < 0) { - perror("pipe"); - return 1; - } - - SharedCtx *ctx = calloc(1, sizeof(SharedCtx)); - ctx->reader_fd = pipefd[0]; - ctx->writer_fd = pipefd[1]; - ctx->fds_reader[0] = 1; - ctx->fds_reader[1] = ctx->reader_fd; - - struct timeval now; - gettimeofday(&now, NULL); - ctx->last_write = (int64_t)now.tv_sec * 1000 + now.tv_usec / 1000; - - sched_create(writer_task, ctx); - sched_create(reader_task, ctx); - sched_create(shutdown_task, ctx); - sched_main(); - return 0; + int pipefd[2]; + if (pipe(pipefd) < 0) { + perror("pipe"); + return 1; + } + + SharedCtx *ctx = calloc(1, sizeof(SharedCtx)); + ctx->reader_fd = pipefd[0]; + ctx->writer_fd = pipefd[1]; + ctx->fds_reader[0] = 1; + ctx->fds_reader[1] = ctx->reader_fd; + + struct timeval now; + gettimeofday(&now, NULL); + ctx->last_write = (int64_t)now.tv_sec * 1000 + now.tv_usec / 1000; + + sched_create(writer_task, ctx); + sched_create(reader_task, ctx); + sched_create(shutdown_task, ctx); + sched_main(); + return 0; } diff --git a/examples/example_periodic.c b/examples/example_periodic.c @@ -1,41 +1,42 @@ -#include "src/scheduler.h" #include <stdio.h> #include <stdlib.h> #include <sys/time.h> +#include "src/scheduler.h" + typedef struct { - int64_t start; - int64_t interval_ms; - int64_t created_at; + int64_t start; + int64_t interval_ms; + int64_t created_at; } TimerCtx; static int ticker(int64_t ts, pt_task_t *task) { - TimerCtx *ctx = task->udata; + TimerCtx *ctx = task->udata; - if (ts - ctx->start >= ctx->interval_ms) { - printf("[%lld ms] tick!\n", (long long)(ts - ctx->created_at)); - ctx->start = ts; + if (ts - ctx->start >= ctx->interval_ms) { + printf("[%lld ms] tick!\n", (long long)(ts - ctx->created_at)); + ctx->start = ts; - if (ts - ctx->created_at >= 5000) { - printf("5 seconds elapsed, stopping.\n"); - free(ctx); - return SCHED_DONE; - } + if (ts - ctx->created_at >= 5000) { + printf("5 seconds elapsed, stopping.\n"); + free(ctx); + return SCHED_DONE; } + } - return SCHED_RUNNING; + return SCHED_RUNNING; } int main(void) { - TimerCtx *ctx = calloc(1, sizeof(TimerCtx)); + TimerCtx *ctx = calloc(1, sizeof(TimerCtx)); - struct timeval now; - gettimeofday(&now, NULL); - ctx->created_at = (int64_t)now.tv_sec * 1000 + now.tv_usec / 1000; - ctx->start = ctx->created_at; - ctx->interval_ms = 1000; + struct timeval now; + gettimeofday(&now, NULL); + ctx->created_at = (int64_t)now.tv_sec * 1000 + now.tv_usec / 1000; + ctx->start = ctx->created_at; + ctx->interval_ms = 1000; - sched_create(ticker, ctx); - sched_main(); - return 0; + sched_create(ticker, ctx); + sched_main(); + return 0; } diff --git a/examples/example_removal.c b/examples/example_removal.c @@ -1,65 +1,66 @@ -#include "src/scheduler.h" #include <stdio.h> #include <stdlib.h> #include <sys/time.h> +#include "src/scheduler.h" + typedef struct { - int64_t start; - int ticks; - int removed; + int64_t start; + int ticks; + int removed; } ChildCtx; typedef struct { - int64_t start; - int64_t max_age_ms; - ChildCtx *child_ctx; - int ticks; + int64_t start; + int64_t max_age_ms; + ChildCtx *child_ctx; + int ticks; } ParentCtx; static int child_task(int64_t ts, pt_task_t *task) { - ChildCtx *ctx = task->udata; - if (ctx->removed) { - free(ctx); - return SCHED_DONE; - } - ctx->ticks++; - printf(" [child] tick %d (age: %lld ms)\n", ctx->ticks, (long long)(ts - ctx->start)); - return SCHED_RUNNING; + ChildCtx *ctx = task->udata; + if (ctx->removed) { + free(ctx); + return SCHED_DONE; + } + ctx->ticks++; + printf(" [child] tick %d (age: %lld ms)\n", ctx->ticks, (long long)(ts - ctx->start)); + return SCHED_RUNNING; } static int parent_task(int64_t ts, pt_task_t *task) { - ParentCtx *ctx = task->udata; - ctx->ticks++; - int64_t age = ts - ctx->start; - printf("[parent] tick %d (elapsed: %lld ms)\n", ctx->ticks, (long long)age); + ParentCtx *ctx = task->udata; + ctx->ticks++; + int64_t age = ts - ctx->start; + printf("[parent] tick %d (elapsed: %lld ms)\n", ctx->ticks, (long long)age); - if (age >= ctx->max_age_ms) { - printf("[parent] child is old enough, signaling it to stop.\n"); - ctx->child_ctx->removed = 1; - free(ctx); - return SCHED_DONE; - } + if (age >= ctx->max_age_ms) { + printf("[parent] child is old enough, signaling it to stop.\n"); + ctx->child_ctx->removed = 1; + free(ctx); + return SCHED_DONE; + } - return SCHED_RUNNING; + return SCHED_RUNNING; } int main(void) { - struct timeval now; - gettimeofday(&now, NULL); - int64_t ts = (int64_t)now.tv_sec * 1000 + now.tv_usec / 1000; + struct timeval now; + gettimeofday(&now, NULL); + int64_t ts = (int64_t)now.tv_sec * 1000 + now.tv_usec / 1000; - ChildCtx *child_ctx = calloc(1, sizeof(ChildCtx)); - child_ctx->start = ts; + ChildCtx *child_ctx = calloc(1, sizeof(ChildCtx)); + child_ctx->start = ts; - ParentCtx *parent_ctx = calloc(1, sizeof(ParentCtx)); - parent_ctx->start = ts; - parent_ctx->max_age_ms = 2500; - parent_ctx->child_ctx = child_ctx; + ParentCtx *parent_ctx = calloc(1, sizeof(ParentCtx)); + parent_ctx->start = ts; + parent_ctx->max_age_ms = 2500; + parent_ctx->child_ctx = child_ctx; - sched_create(child_task, child_ctx); - sched_create(parent_task, parent_ctx); - sched_main(); + sched_create(child_task, child_ctx); + sched_create(parent_task, parent_ctx); + sched_main(); - printf("Both tasks are gone. Goodbye.\n"); - return 0; + printf("Both tasks are gone. Goodbye.\n"); + return 0; } diff --git a/examples/example_sem.c b/examples/example_sem.c @@ -0,0 +1,57 @@ +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> + +#include "src/sched-sem.h" +#include "src/scheduler.h" + +typedef struct { + struct sched_sem sem; + int tokens_produced; + int tokens_consumed; +} ProducerCtx; + +typedef struct { + struct sched_sem *sem; + int *tokens_consumed; +} ConsumerCtx; + +static int producer(int64_t ts, pt_task_t *task) { + ProducerCtx *ctx = task->udata; + (void)ts; + + sched_sem_signal(&ctx->sem, 1); + ctx->tokens_produced++; + + if (ctx->tokens_produced >= 10) { + printf("Producer done (produced %d tokens)\n", ctx->tokens_produced); + return SCHED_DONE; + } + return SCHED_RUNNING; +} + +static int consumer(int64_t ts, pt_task_t *task) { + ConsumerCtx *ctx = task->udata; + (void)ts; + + sched_sem_wait(ctx->sem); + (*ctx->tokens_consumed)++; + + if (*ctx->tokens_consumed >= 10) { + printf("Consumer done (consumed %d tokens)\n", *ctx->tokens_consumed); + return SCHED_DONE; + } + return SCHED_RUNNING; +} + +int main(void) { + ProducerCtx producer_ctx = {.sem = {.value = 0}, .tokens_produced = 0}; + int tokens_consumed = 0; + ConsumerCtx consumer_ctx = {.sem = &producer_ctx.sem, .tokens_consumed = &tokens_consumed}; + + sched_create(producer, &producer_ctx); + sched_create(consumer, &consumer_ctx); + sched_main(); + + return 0; +} diff --git a/src/sched-sem.c b/src/sched-sem.c @@ -0,0 +1,13 @@ +#include "sched-sem.h" + +void sched_sem_init(struct sched_sem *sem, int amount) { + sem->value = amount; +} + +void sched_sem_signal(struct sched_sem *sem, int amount) { + sem->value += amount; +} + +void sched_sem_consume(struct sched_sem *sem, int amount) { + sem->value -= amount; +} diff --git a/src/sched-sem.h b/src/sched-sem.h @@ -0,0 +1,17 @@ +#ifndef __FINWO_SCHEDULER_SEM_H__ +#define __FINWO_SCHEDULER_SEM_H__ + +struct sched_sem { + int value; +}; + +void sched_sem_init(struct sched_sem *sem, int amount); +void sched_sem_signal(struct sched_sem *sem, int amount); +void sched_sem_consume(struct sched_sem *sem, int amount); + +#define sched_sem_wait(sem) \ + do { \ + if ((sem)->value <= 0) return SCHED_RUNNING; \ + } while (0) + +#endif // __FINWO_SCHEDULER_SEM_H__