Skip to content

Latest commit

 

History

History
2389 lines (1570 loc) · 63.9 KB

API.md

File metadata and controls

2389 lines (1570 loc) · 63.9 KB

Neco C API

C API for the Neco coroutine library.

This document provides a detailed description of the functions and types in the neco.h and neco.c source files for the Neco library.

For a more general overview please see the project README.

Table of contents

Programming notes

neco_main

The neco_main() function may be used instead of the standard C main(), which effectively runs the entire program in a Neco coroutine context.

#include "neco.h"

int neco_main(int argc, char *argv[]) {
    // Running inside of a Neco coroutine
    return 0;
}

Doing so adjusts Neco to behave as follows:

  • neco_env_setpaniconerror(true)
  • neco_env_setcanceltype(NECO_CANCEL_ASYNC)
  • The program will exit after the the main coroutine returns.

Neco errors

Neco functions return Neco errors.

#define NECO_OK              0  ///< Successful result (no error)
#define NECO_ERROR          -1  ///< System error (check errno)
#define NECO_INVAL          -2  ///< Invalid argument
#define NECO_PERM           -3  ///< Operation not permitted
#define NECO_NOMEM          -4  ///< Cannot allocate memory
#define NECO_EOF            -5  ///< End of file or stream (neco_stream_*)
#define NECO_NOTFOUND       -6  ///< No such coroutine (neco_cancel)
#define NECO_NOSIGWATCH     -7  ///< Not watching on a signal
#define NECO_CLOSED         -8  ///< Channel is closed
#define NECO_EMPTY          -9  ///< Channel is empty (neco_chan_tryrecv)
#define NECO_TIMEDOUT      -10  ///< Deadline has elapsed (by neco_*_dl)
#define NECO_CANCELED      -11  ///< Operation canceled (by neco_cancel)
#define NECO_BUSY          -12  ///< Resource busy (by mutex_trylock)
#define NECO_NEGWAITGRP    -13  ///< Negative waitgroup counter
#define NECO_GAIERROR      -14  ///< Error with getaddrinfo (check neco_gai_error)
#define NECO_UNREADFAIL    -15  ///< Failed to unread byte (neco_stream_unread_byte)
#define NECO_PARTIALWRITE  -16  ///< Failed to write all data (neco_stream_flush)
#define NECO_NOTGENERATOR  -17  ///< Coroutine is not a generator (neco_gen_yield)
#define NECO_NOTSUSPENDED  -18  ///< Coroutine is not suspended (neco_resume)

Three of those errors will panic when neco_env_setpaniconerror(true): NECO_INVAL, NECO_PERM, and NECO_NOMEM.

Some Neco functions are Posix wrappers, which return the NECO_ERROR (-1) and it's the programmers responsibilty to check the errno. Those functions include neco_read(), neco_write(), neco_accept(), and neco_connect().

At any point the programmer may check the neco_lasterr() function to get the Neco error from the last neco_* call. This function will ensure that system errors will be automatically converted to its Neco equivalent. For example, let's say that a neco_read() (which is Posix wrapper for read()) returns -1 and the errno is EINVAL, then neco_lasterr() will return NECO_INVAL.

Deadlines and cancelation

All operations that may block a coroutine will have an extended function with a _dl suffix that provides an additional deadline argument. A deadline is a timestamp in nanoseconds. NECO_TIMEDOUT will be returned if the operation does not complete in the provide time.

All operations that may block can also be canceled using the neco_cancel() function from a different coroutine. Calling neco_cancel() will not cancel an operation that does not block. A cancel takes higher priority than a deadline. Such that if an operation was canceled and also has timedout at the same time the cancel wins and NECO_CANCELED will be returned.

// Try to connect, timing out after one second.
int fd = neco_dial_dl("tcp", "google.com:80", neco_now() + NECO_SECOND);
if (fd < 0) {
    // Connection failed
    if (fd == NECO_CANCELED) {
        // Operation canceled
    } else if (fd == NECO_TIMEDOUT) {
        // Operation timedout
    } else {
        // Some other error
    }
    return;
}
// Success

Async cancelation

By default, when a coroutine is canceled it's the responsibility of the programmer to check the return value and perform any clean up. But it's also possible to have Neco handle all that by enabling the async cancelation with neco_setcanceltype(NECO_CANCEL_ASYNC, 0) at the coroutine level or neco_env_setcanceltype(NECO_CANCEL_ASYNC) globally from the main function.

When async cancelation is enabled, the coroutine that is canceled is terminated right away, and any cleanup handled using the neco_cleanup_push() and neco_cleanup_pop() functions.

Async cancelation is automatically enabled when using neco_main(), instead of main(), as the program startup function.

Disabling cancelation

Finally, coroutine cancelation can be totally disabled by calling neco_setcancelstate(NECO_CANCEL_DISABLE, 0) at the coroutine level or neco_env_setcancelstate(NECO_CANCEL_DISABLE) globally from the main function.

Basic operations

Neco provides standard operations for starting a coroutine, sleeping, suspending, resuming, yielding to another coroutine, joining/waiting for child coroutines, and exiting a running coroutine.

Channels

Channels allow for sending and receiving values between coroutines. By default, sends and receives will block until the other side is ready. This allows the coroutines to synchronize without using locks or condition variables.

Generators

A generator is a specialized iterator-bound coroutine that can produce a sequence of values to be iterated over.

Mutexes

A mutex is synchronization mechanism that blocks access to variables by multiple coroutines at once. This enforces exclusive access by a coroutine to a variable or set of variables and helps to avoid data inconsistencies due to race conditions.

WaitGroups

A WaitGroup waits for a multiple coroutines to finish. The main coroutine calls neco_waitgroup_add() to set the number of coroutines to wait for. Then each of the coroutines runs and calls neco_waitgroup_done() when complete. At the same time, neco_waitgroup_wait() can be used to block until all coroutines are completed.

Condition variables

A condition variable is a synchronization mechanism that allows coroutines to suspend execution until some condition is true.

Posix wrappers

Functions that work like their Posix counterpart but do not block, allowing for usage in a Neco coroutine.

File descriptor helpers

Functions for working with file descriptors.

Networking utilities

Cancelation

Random number generator

Signals

Allows for signals, such as SIGINT (Ctrl-C), to be intercepted or ignored.

Background worker

Run arbritary code in a background worker thread

Stats and information

Global environment

Time

Functions for working with time.

The following defines are available for convenience.

#define NECO_NANOSECOND  INT64_C(1)
#define NECO_MICROSECOND INT64_C(1000)
#define NECO_MILLISECOND INT64_C(1000000)
#define NECO_SECOND      INT64_C(1000000000)
#define NECO_MINUTE      INT64_C(60000000000)
#define NECO_HOUR        INT64_C(3600000000000)

Error handling

Functions for working with Neco errors.

Streams and Buffered I/O

Create a Neco stream from a file descriptor using neco_stream_make() or a buffered stream using neco_stream_make_buffered().

neco_stats

struct neco_stats {
    size_t coroutines;   // Number of active coroutines. 
    size_t sleepers;     // Number of sleeping coroutines. 
    size_t evwaiters;    // Number of coroutines waiting on I/O events. 
    size_t sigwaiters;  
    size_t senders;     
    size_t receivers;   
    size_t locked;      
    size_t waitgroupers;
    size_t condwaiters; 
    size_t suspended;   
    size_t workers;      // Number of background worker threads. 
};

neco_mutex

struct neco_mutex {
    int64_t rtid;       
    bool locked;        
    int rlocked;        
    struct colist queue;
    char _;             
};

neco_waitgroup

struct neco_waitgroup {
    int64_t rtid;       
    int count;          
    struct colist queue;
    char _;             
};

neco_cond

struct neco_cond {
    int64_t rtid;       
    struct colist queue;
    char _;             
};

neco_chan

struct neco_chan;

neco_stream

struct neco_stream;

neco_gen

struct neco_gen;

neco_start()

int neco_start(void(*coroutine)(int argc, void *argv[]), int argc,...);

Starts a new coroutine.

If this is the first coroutine started for the program (or thread) then this will also create a neco runtime scheduler which blocks until the provided coroutine and all of its subsequent child coroutines finish.

Example

// Here we'll start a coroutine that prints "hello world".

void coroutine(int argc, void *argv[]) {
    char *msg = argv[0];
    printf("%s\n", msg);
}

neco_start(coroutine, 1, "hello world");

Parameters

  • coroutine: The coroutine that will soon run
  • argc: Number of arguments
  • ...: Arguments passed to the coroutine

Return

  • NECO_OK Success
  • NECO_NOMEM The system lacked the necessary resources
  • NECO_INVAL An invalid parameter was provided

neco_startv()

int neco_startv(void(*coroutine)(int argc, void *argv[]), int argc, void *argv[]);

Starts a new coroutine using an array for arguments.

See also

neco_yield()

int neco_yield();

Cause the calling coroutine to relinquish the CPU. The coroutine is moved to the end of the queue.

Return

  • NECO_OK Success
  • NECO_PERM Operation called outside of a coroutine

neco_sleep()

int neco_sleep(int64_t nanosecs);

Causes the calling coroutine to sleep until the number of specified nanoseconds have elapsed.

Parameters

  • nanosecs: duration nanoseconds

Return

  • NECO_OK Coroutine slept until nanosecs elapsed
  • NECO_TIMEDOUT nanosecs is a negative number
  • NECO_CANCELED Operation canceled
  • NECO_PERM Operation called outside of a coroutine

See also

neco_sleep_dl()

int neco_sleep_dl(int64_t deadline);

Same as neco_sleep() but with a deadline parameter.

neco_join()

int neco_join(int64_t id);

Wait for a coroutine to terminate. If that coroutine has already terminated or is not found, then this operation returns immediately.

Example

// Start a new coroutine
neco_start(coroutine, 0);

// Get the identifier of the new coroutine.
int64_t id = neco_lastid();

// Wait until the coroutine has terminated.
neco_join(id);

Parameters

  • id: Coroutine identifier

Return

  • NECO_OK Success
  • NECO_CANCELED Operation canceled
  • NECO_PERM Operation called outside of a coroutine

See also

neco_join_dl()

int neco_join_dl(int64_t id, int64_t deadline);

Same as neco_join() but with a deadline parameter.

neco_suspend()

int neco_suspend();

Suspend the current coroutine.

Return

  • NECO_OK Success
  • NECO_PERM Operation called outside of a coroutine
  • NECO_CANCELED Operation canceled

See also

neco_suspend_dl()

int neco_suspend_dl(int64_t deadline);

Same as neco_suspend() but with a deadline parameter.

neco_resume()

int neco_resume(int64_t id);

Resume a suspended roroutine

Return

  • NECO_OK Success
  • NECO_PERM Operation called outside of a coroutine
  • NECO_NOTFOUND Coroutine not found
  • NECO_NOTSUSPENDED Coroutine not suspended

See also

neco_exit()

void neco_exit();

Terminate the current coroutine.

Any clean-up handlers established by neco_cleanup_push() that have not yet been popped, are popped (in the reverse of the order in which they were pushed) and executed.

Calling this from outside of a coroutine context does nothing and will be treated effectivley as a no-op.

neco_getid()

int64_t neco_getid();

Returns the identifier for the currently running coroutine.

This value is guaranteed to be unique for the duration of the program.

Return

  • The coroutine identifier
  • NECO_PERM Operation called outside of a coroutine

neco_lastid()

int64_t neco_lastid();

Returns the identifier for the coroutine started by the current coroutine.

For example, here a coroutine is started and its identifer is then retreived.

neco_start(coroutine, 0);
int64_t id = neco_lastid();

Return

  • A coroutine identifier, or zero if the current coroutine has not yet started any coroutines.
  • NECO_PERM Operation called outside of a coroutine

neco_starterid()

int64_t neco_starterid();

Get the identifier for the coroutine that started the current coroutine.

void child_coroutine(int argc, void *argv[]) {
    int parent_id = neco_starterid();
    // The parent_id is equal as the neco_getid() from the parent_coroutine
    // below. 
}

void parent_coroutine(int argc, void *argv[]) {
   int id = neco_getid();
   neco_start(child_coroutine, 0);
}

Return

  • A coroutine identifier, or zero if the coroutine is the first coroutine started.

neco_chan_make()

int neco_chan_make(neco_chan **chan, size_t data_size, size_t capacity);

Creates a new channel for sharing messages with other coroutines.

Example

void coroutine(int argc, void *argv[]) {
    neco_chan *ch = argv[0];

    // Send a message
    neco_chan_send(ch, &(int){ 1 });

    // Release the channel
    neco_chan_release(ch);
}

int neco_start(int argc, char *argv[]) {
    neco_chan *ch;
    neco_chan_make(&ch, sizeof(int), 0);
    
    // Retain a reference of the channel and provide it to a newly started
    // coroutine. 
    neco_chan_retain(ch);
    neco_start(coroutine, 1, ch);
    
    // Receive a message
    int msg;
    neco_chan_recv(ch, &msg);
    printf("%d\n", msg);      // prints '1'
    
    // Always release the channel when you are done
    neco_chan_release(ch);
}

Parameters

  • chan: Channel
  • data_size: Data size of messages
  • capacity: Buffer capacity

Return

  • NECO_OK Success
  • NECO_NOMEM The system lacked the necessary resources
  • NECO_INVAL An invalid parameter was provided
  • NECO_PERM Operation called outside of a coroutine

Note

  • The caller is responsible for freeing with neco_chan_release()
  • data_size and capacity cannot be greater than INT_MAX

neco_chan_retain()

int neco_chan_retain(neco_chan *chan);

Retain a reference of the channel so it can be shared with other coroutines.

This is needed for avoiding use-after-free bugs.

See neco_chan_make() for an example.

Parameters

  • chan: The channel

Return

  • NECO_OK Success
  • NECO_INVAL An invalid parameter was provided
  • NECO_PERM Operation called outside of a coroutine

Note

See also

neco_chan_release()

int neco_chan_release(neco_chan *chan);

Release a reference to a channel

See neco_chan_make() for an example.

Parameters

  • chan: The channel

Return

  • NECO_OK Success
  • NECO_INVAL An invalid parameter was provided
  • NECO_PERM Operation called outside of a coroutine

See also

neco_chan_send()

int neco_chan_send(neco_chan *chan, void *data);

Send a message

See neco_chan_make() for an example.

Return

  • NECO_OK Success
  • NECO_PERM Operation called outside of a coroutine
  • NECO_INVAL An invalid parameter was provided
  • NECO_CANCELED Operation canceled
  • NECO_CLOSED Channel closed

See also

neco_chan_send_dl()

int neco_chan_send_dl(neco_chan *chan, void *data, int64_t deadline);

Same as neco_chan_send() but with a deadline parameter.

neco_chan_broadcast()

int neco_chan_broadcast(neco_chan *chan, void *data);

Sends message to all receiving channels.

Return

  • The number of channels that received the message
  • NECO_PERM Operation called outside of a coroutine
  • NECO_INVAL An invalid parameter was provided
  • NECO_CLOSED Channel closed

Note

  • This operation cannot be canceled and does not timeout

See also

neco_chan_recv()

int neco_chan_recv(neco_chan *chan, void *data);

Receive a message

See neco_chan_make() for an example.

Parameters

  • chan: channel
  • data: data pointer

Return

  • NECO_OK Success
  • NECO_PERM Operation called outside of a coroutine
  • NECO_INVAL An invalid parameter was provided
  • NECO_CANCELED Operation canceled
  • NECO_CLOSED Channel closed

See also

neco_chan_recv_dl()

int neco_chan_recv_dl(neco_chan *chan, void *data, int64_t deadline);

Same as neco_chan_recv() but with a deadline parameter.

neco_chan_tryrecv()

int neco_chan_tryrecv(neco_chan *chan, void *data);

Receive a message, but do not wait if the message is not available.

Parameters

  • chan: channel
  • data: data pointer

Return

  • NECO_OK Success
  • NECO_EMPTY No message available
  • NECO_CLOSED Channel closed
  • NECO_PERM Operation called outside of a coroutine
  • NECO_INVAL An invalid parameter was provided

See also

neco_chan_close()

int neco_chan_close(neco_chan *chan);

Close a channel for sending.

Parameters

  • chan: channel

Return

  • NECO_OK Success
  • NECO_PERM Operation called outside of a coroutine
  • NECO_INVAL An invalid parameter was provided
  • NECO_CLOSED Channel already closed

neco_chan_select()

int neco_chan_select(int nchans,...);

Wait on multiple channel operations at the same time.

Example

// Let's say we have two channels 'c1' and 'c2' that both transmit 'char *'
// messages.

// Use neco_chan_select() to wait on both channels.

char *msg;
int idx = neco_chan_select(2, c1, c2);
switch (idx) {
case 0:
    neco_chan_case(c1, &msg);
    break;
case 1:
    neco_chan_case(c2, &msg);
    break;
default:
    // Error occured. The return value 'idx' is the error
}

printf("%s\n", msg);

Parameters

  • nchans: Number of channels
  • ...: The channels

Return

  • The index of channel with an available message
  • NECO_PERM Operation called outside of a coroutine
  • NECO_INVAL An invalid parameter was provided
  • NECO_NOMEM The system lacked the necessary resources
  • NECO_CANCELED Operation canceled

See also

neco_chan_select_dl()

int neco_chan_select_dl(int64_t deadline, int nchans,...);

Same as neco_chan_select() but with a deadline parameter.

neco_chan_selectv()

int neco_chan_selectv(int nchans, neco_chan *chans[]);

Same as neco_chan_select() but using an array for arguments.

neco_chan_selectv_dl()

int neco_chan_selectv_dl(int nchans, neco_chan *chans[], int64_t deadline);

Same as neco_chan_selectv() but with a deadline parameter.

neco_chan_tryselect()

int neco_chan_tryselect(int nchans,...);

Same as neco_chan_select() but does not wait if a message is not available

Return

  • NECO_EMPTY No message available

See also

neco_chan_tryselectv()

int neco_chan_tryselectv(int nchans, neco_chan *chans[]);

Same as neco_chan_tryselect() but uses an array for arguments.

neco_chan_case()

int neco_chan_case(neco_chan *chan, void *data);

Receive the message after a successful neco_chan_select(). See neco_chan_select() for an example.

Parameters

  • chan: The channel
  • data: The data

Return

  • NECO_OK Success
  • NECO_PERM Operation called outside of a coroutine
  • NECO_INVAL An invalid parameter was provided
  • NECO_CLOSED Channel closed

neco_gen_start()

int neco_gen_start(neco_gen **gen, size_t data_size, void(*coroutine)(int argc, void *argv[]), int argc,...);

Start a generator coroutine

Example

void coroutine(int argc, void *argv[]) {
    // Yield each int to the caller, one at a time.
    for (int i = 0; i < 10; i++) {
        neco_gen_yield(&i);
    }
}

int neco_main(int argc, char *argv[]) {
    
    // Create a new generator coroutine that is used to send ints.
    neco_gen *gen;
    neco_gen_start(&gen, sizeof(int), coroutine, 0);

    // Iterate over each int until the generator is closed.
    int i;
    while (neco_gen_next(gen, &i) != NECO_CLOSED) {
        printf("%d\n", i); 
    }

    // This coroutine no longer needs the generator.
    neco_gen_release(gen);
    return 0;
}

Parameters

  • gen: Generator object
  • data_size: Data size of messages
  • coroutine: Generator coroutine
  • argc: Number of arguments

Return

  • NECO_OK Success
  • NECO_NOMEM The system lacked the necessary resources
  • NECO_INVAL An invalid parameter was provided
  • NECO_PERM Operation called outside of a coroutine

Note

  • The caller is responsible for freeing the generator object with with neco_gen_release().

neco_gen_startv()

int neco_gen_startv(neco_gen **gen, size_t data_size, void(*coroutine)(int argc, void *argv[]), int argc, void *argv[]);

Same as neco_gen_start() but using an array for arguments.

neco_gen_retain()

int neco_gen_retain(neco_gen *gen);

Retain a reference of the generator so it can be shared with other coroutines.

This is needed for avoiding use-after-free bugs.

See neco_gen_start() for an example.

Parameters

  • gen: The generator

Return

  • NECO_OK Success
  • NECO_INVAL An invalid parameter was provided
  • NECO_PERM Operation called outside of a coroutine

Note

See also

neco_gen_release()

int neco_gen_release(neco_gen *gen);

Release a reference to a generator

See neco_gen_start() for an example.

Parameters

  • gen: The generator

Return

  • NECO_OK Success
  • NECO_INVAL An invalid parameter was provided
  • NECO_PERM Operation called outside of a coroutine

See also

neco_gen_yield()

int neco_gen_yield(void *data);

Send a value to the generator for the next iteration.

See neco_gen_start() for an example.

neco_gen_yield_dl()

int neco_gen_yield_dl(void *data, int64_t deadline);

Same as neco_gen_yield() but with a deadline parameter.

neco_gen_next()

int neco_gen_next(neco_gen *gen, void *data);

Receive the next value from a generator.

See neco_gen_start() for an example.

neco_gen_next_dl()

int neco_gen_next_dl(neco_gen *gen, void *data, int64_t deadline);

Same as neco_gen_next() but with a deadline parameter.

neco_gen_close()

int neco_gen_close(neco_gen *gen);

Close the generator.

Parameters

  • gen: Generator

Return

  • NECO_OK Success

neco_mutex_init()

int neco_mutex_init(neco_mutex *mutex);

neco_mutex_lock()

int neco_mutex_lock(neco_mutex *mutex);

neco_mutex_lock_dl()

int neco_mutex_lock_dl(neco_mutex *mutex, int64_t deadline);

neco_mutex_trylock()

int neco_mutex_trylock(neco_mutex *mutex);

neco_mutex_unlock()

int neco_mutex_unlock(neco_mutex *mutex);

neco_mutex_rdlock()

int neco_mutex_rdlock(neco_mutex *mutex);

neco_mutex_rdlock_dl()

int neco_mutex_rdlock_dl(neco_mutex *mutex, int64_t deadline);

neco_mutex_tryrdlock()

int neco_mutex_tryrdlock(neco_mutex *mutex);

neco_waitgroup_init()

int neco_waitgroup_init(neco_waitgroup *waitgroup);

neco_waitgroup_add()

int neco_waitgroup_add(neco_waitgroup *waitgroup, int delta);

neco_waitgroup_done()

int neco_waitgroup_done(neco_waitgroup *waitgroup);

neco_waitgroup_wait()

int neco_waitgroup_wait(neco_waitgroup *waitgroup);

neco_waitgroup_wait_dl()

int neco_waitgroup_wait_dl(neco_waitgroup *waitgroup, int64_t deadline);

neco_cond_init()

int neco_cond_init(neco_cond *cond);

neco_cond_signal()

int neco_cond_signal(neco_cond *cond);

neco_cond_broadcast()

int neco_cond_broadcast(neco_cond *cond);

neco_cond_wait()

int neco_cond_wait(neco_cond *cond, neco_mutex *mutex);

neco_cond_wait_dl()

int neco_cond_wait_dl(neco_cond *cond, neco_mutex *mutex, int64_t deadline);

neco_read()

ssize_t neco_read(int fd, void *data, size_t nbytes);

Read from a file descriptor.

This operation attempts to read up to count from file descriptor fd into the buffer starting at buf.

This is a Posix wrapper function for the purpose of running in a Neco coroutine. It's expected that the provided file descriptor is in non-blocking state.

Return

  • On success, the number of bytes read is returned (zero indicates end of file)
  • On error, value -1 (NECO_ERROR) is returned, and errno is set to indicate the error.

See also

neco_read_dl()

ssize_t neco_read_dl(int fd, void *data, size_t nbytes, int64_t deadline);

Same as neco_read() but with a deadline parameter.

neco_write()

ssize_t neco_write(int fd, const void *data, size_t nbytes);

Write to a file descriptor.

This operation attempts to write all bytes in the buffer starting at buf to the file referred to by the file descriptor fd.

This is a Posix wrapper function for the purpose of running in a Neco coroutine. It's expected that the provided file descriptor is in non-blocking state.

One difference from the Posix version is that this function will attempt to write all bytes in buffer. The programmer, at their discretion, may considered it as an error when fewer than count is returned. If so, the neco_lasterr() will return the NECO_PARTIALWRITE.

Return

  • On success, the number of bytes written is returned.
  • On error, value -1 (NECO_ERROR) is returned, and errno is set to indicate the error.

See also

neco_write_dl()

ssize_t neco_write_dl(int fd, const void *data, size_t nbytes, int64_t deadline);

Same as neco_write() but with a deadline parameter.

neco_accept()

int neco_accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

Accept a connection on a socket.

While in a coroutine, this function should be used instead of the standard accept() to avoid blocking other coroutines from running concurrently.

The the accepted file descriptor is returned in non-blocking mode.

Parameters

  • sockfd: Socket file descriptor
  • addr: Socket address out
  • addrlen: Socket address length out

Return

  • On success, file descriptor (non-blocking)
  • On error, value -1 (NECO_ERROR) is returned, and errno is set to indicate the error.

See also

neco_accept_dl()

int neco_accept_dl(int sockfd, struct sockaddr *addr, socklen_t *addrlen, int64_t deadline);

Same as neco_accept() but with a deadline parameter.

neco_connect()

int neco_connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

Connects the socket referred to by the file descriptor sockfd to the address specified by addr.

While in a coroutine, this function should be used instead of the standard connect() to avoid blocking other coroutines from running concurrently.

Parameters

  • sockfd: Socket file descriptor
  • addr: Socket address out
  • addrlen: Socket address length out

Return

  • NECO_OK Success
  • On error, value -1 (NECO_ERROR) is returned, and errno is set to indicate the error.

neco_connect_dl()

int neco_connect_dl(int sockfd, const struct sockaddr *addr, socklen_t addrlen, int64_t deadline);

Same as neco_connect() but with a deadline parameter.

neco_getaddrinfo()

int neco_getaddrinfo(const char *node, const char *service, const struct addrinfo *hints, struct addrinfo **res);

The getaddrinfo() function is used to get a list of addresses and port numbers for node (hostname) and service.

This is functionally identical to the Posix getaddrinfo function with the exception that it does not block, allowing for usage in a Neco coroutine.

Return

  • On success, 0 is returned
  • On error, a nonzero error code defined by the system. See the link below for a list.

See also

neco_getaddrinfo_dl()

int neco_getaddrinfo_dl(const char *node, const char *service, const struct addrinfo *hints, struct addrinfo **res, int64_t deadline);

Same as neco_getaddrinfo() but with a deadline parameter.

neco_setnonblock()

int neco_setnonblock(int fd, bool nonblock, bool *oldnonblock);

Change the non-blocking state for a file descriptor.

See also

neco_wait()

int neco_wait(int fd, int mode);

Wait for a file descriptor to be ready for reading or writing.

Normally one should use neco_read() and neco_write() to read and write data. But there may be times when you need more involved logic or to use alternative functions such as recvmsg() or sendmsg().

while (1) {
    int n = recvmsg(sockfd, msg, MSG_DONTWAIT);
    if (n == -1) {
        if (errno == EAGAIN) {
            // The socket is not ready for reading.
            neco_wait(sockfd, NECO_WAIT_READ);
            continue;
        }
        // Socket error.
        return;
    }
    // Message received.
    break;
}

Parameters

  • fd: The file descriptor
  • mode: NECO_WAIT_READ or NECO_WAIT_WRITE

Return

  • NECO_OK Success
  • NECO_NOMEM The system lacked the necessary resources
  • NECO_INVAL An invalid parameter was provided
  • NECO_PERM Operation called outside of a coroutine
  • NECO_ERROR Check errno for more info

See also

neco_wait_dl()

int neco_wait_dl(int fd, int mode, int64_t deadline);

Same as neco_wait() but with a deadline parameter.

neco_serve()

int neco_serve(const char *network, const char *address);

Listen on a local network address.

Example

int servefd = neco_serve("tcp", "127.0.0.1:8080");
if (servefd < 0) {
   // .. error, do something with it.
}
while (1) {
    int fd = neco_accept(servefd, 0, 0);
    // client accepted
}

close(servefd);

Parameters

  • network: must be "tcp", "tcp4", "tcp6", or "unix".
  • address: the address to serve on

Return

  • On success, file descriptor (non-blocking)
  • On error, Neco error

See also

neco_serve_dl()

int neco_serve_dl(const char *network, const char *address, int64_t deadline);

Same as neco_serve() but with a deadline parameter.

neco_dial()

int neco_dial(const char *network, const char *address);

Connect to a remote server.

Example

int fd = neco_dial("tcp", "google.com:80");
if (fd < 0) {
   // .. error, do something with it.
}
// Connected to google.com. Use neco_read(), neco_write(), or create a 
// stream using neco_stream_make(fd).
close(fd);

Parameters

  • network: must be "tcp", "tcp4", "tcp6", or "unix".
  • address: the address to dial

Return

  • On success, file descriptor (non-blocking)
  • On error, Neco error

See also

neco_dial_dl()

int neco_dial_dl(const char *network, const char *address, int64_t deadline);

Same as neco_dial() but with a deadline parameter.

neco_cancel()

int neco_cancel(int64_t id);

neco_cancel_dl()

int neco_cancel_dl(int64_t id, int64_t deadline);

neco_setcanceltype()

int neco_setcanceltype(int type, int *oldtype);

neco_setcancelstate()

int neco_setcancelstate(int state, int *oldstate);

neco_rand_setseed()

int neco_rand_setseed(int64_t seed, int64_t *oldseed);

Set the random seed for the Neco pseudorandom number generator.

The provided seed is only used for the (non-crypto) NECO_PRNG and is ignored for NECO_CPRNG.

Parameters

  • seed:
  • oldseed[out]: The previous seed

Return

  • NECO_OK Success
  • NECO_INVAL An invalid parameter was provided
  • NECO_PERM Operation called outside of a coroutine

See also

neco_rand()

int neco_rand(void *data, size_t nbytes, int attr);

Generator random bytes

This operation can generate cryptographically secure data by providing the NECO_CSPRNG option or non-crypto secure data with NECO_PRNG.

Non-crypto secure data use the pcg-family random number generator.

Parameters

  • data: buffer for storing random bytes
  • nbytes: number of bytes to generate
  • attr: NECO_PRNG or NECO_CSPRNG

Return

  • NECO_OK Success
  • NECO_INVAL An invalid parameter was provided
  • NECO_PERM Operation called outside of a coroutine
  • NECO_CANCELED Operation canceled

neco_rand_dl()

int neco_rand_dl(void *data, size_t nbytes, int attr, int64_t deadline);

Same as neco_rand() but with a deadline parameter.

neco_signal_watch()

int neco_signal_watch(int signo);

Have the current coroutine watch for a signal.

This can be used to intercept or ignore signals.

Signals that can be watched: SIGHUP, SIGINT, SIGQUIT, SIGTERM, SIGPIPE, SIGUSR1, SIGUSR2, SIGALRM

Signals that can not be watched: SIGILL, SIGFPE, SIGSEGV, SIGBUS, SIGTRAP

Example

// Set up this coroutine to watich for the SIGINT (Ctrl-C) and SIGQUIT
// (Ctrl-\) signals.
neco_signal_watch(SIGINT);
neco_signal_watch(SIGQUIT);

printf("Waiting for Ctrl-C or Ctrl-\\ signals...\n");
int sig = neco_signal_wait();
if (sig == SIGINT) {
    printf("\nReceived Ctrl-C\n");
} else if (sig == SIGQUIT) {
    printf("\nReceived Ctrl-\\\n");
}

// The neco_signal_unwatch is used to stop watching.
neco_signal_unwatch(SIGINT);
neco_signal_unwatch(SIGQUIT);

Parameters

  • signo: The signal number

Return

  • NECO_OK Success
  • NECO_INVAL An invalid parameter was provided
  • NECO_PERM Operation called outside of a coroutine

See also

neco_signal_wait()

int neco_signal_wait();

Wait for a signal to arrive.

Return

  • A signal number or an error.
  • NECO_PERM
  • NECO_NOSIGWATCH if not currently watching for signals.
  • NECO_CANCELED

See also

neco_signal_wait_dl()

int neco_signal_wait_dl(int64_t deadline);

Same as neco_signal_wait() but with a deadline parameter.

neco_signal_unwatch()

int neco_signal_unwatch(int signo);

Stop watching for a siganl to arrive

Parameters

  • signo: The signal number

Return

  • NECO_OK on success or an error

neco_work()

int neco_work(int64_t pin, void(*work)(void *udata), void *udata);

Perform work in a background thread and wait until the work is done.

This operation cannot be canceled and cannot timeout. It's the responibilty of the caller to figure out a mechanism for doing those things from inside of the work function.

The work function will not be inside of a Neco context, thus all neco_* functions will fail if called from inside of the work function.

Parameters

  • pin: pin to a thread, or use -1 for round robin selection.
  • work: the work, must not be null
  • udata: any user data

Return

  • NECO_OK Success
  • NECO_NOMEM The system lacked the necessary resources
  • NECO_INVAL An invalid parameter was provided

Note

  • There is no way to cancel or timeout this operation
  • There is no way to cancel or timeout this operation

neco_getstats()

int neco_getstats(neco_stats *stats);

Returns various stats for the current Neco runtime.

// Print the number of active coroutines
neco_stats stats;
neco_getstats(&stats);
printf("%zu\n", stats.coroutines);

Other stats include:

coroutines
sleepers
evwaiters
sigwaiters
senders
receivers
locked
waitgroupers
condwaiters
suspended

neco_is_main_thread()

int neco_is_main_thread();

Test if coroutine is running on main thread.

Return

  • 1 for true, 0 for false, or a negative value for error.

neco_switch_method()

const char *neco_switch_method();

neco_env_setallocator()

void neco_env_setallocator(void *(*malloc)(size_t), void *(*realloc)(void *, size_t), void(*free)(void *));

Globally set the allocators for all Neco functions.

This should only be run once at program startup and before the first neco_start function is called.

See also

neco_env_setpaniconerror()

void neco_env_setpaniconerror(bool paniconerror);

Globally set the panic-on-error state for all coroutines.

This will cause panics (instead of returning the error) for three errors: NECO_INVAL, NECO_PERM, and NECO_NOMEM.

This should only be run once at program startup and before the first neco_start function is called.

See also

neco_env_setcanceltype()

void neco_env_setcanceltype(int type);

Globally set the canceltype for all coroutines.

This should only be run once at program startup and before the first neco_start function is called.

See also

neco_env_setcancelstate()

void neco_env_setcancelstate(int state);

Globally set the cancelstate for all coroutines.

This should only be run once at program startup and before the first neco_start function is called.

See also

neco_now()

int64_t neco_now();

Get the current time.

This operation calls gettime(CLOCK_MONOTONIC) to retreive a monotonically increasing value that is not affected by discontinuous jumps in the system time.

This value IS NOT the same as the local system time; for that the user should call gettime(CLOCK_REALTIME).

The main purpose of this function to work with operations that use deadlines, i.e. functions with the *_dl() suffix.

Example

// Receive a message from a channel using a deadline of one second from now.
int ret = neco_chan_recv_dl(ch, &msg, neco_now() + NECO_SECOND);
if (ret == NECO_TIMEDOUT) {
    // The operation timed out
}

Return

  • On success, the current time as nanoseconds.
  • NECO_PERM Operation called outside of a coroutine

See also

neco_strerror()

const char *neco_strerror(ssize_t errcode);

Returns a string representation of an error code.

See also

  • Errors

neco_lasterr()

int neco_lasterr();

Returns last known error from a Neco operation

See Neco errors for a list.

neco_gai_lasterr()

int neco_gai_lasterr();

Get the last error from a neco_getaddrinfo() call.

See the man page for a list of errors.

neco_panic()

int neco_panic(const char *fmt,...);

Stop normal execution of the current coroutine, print stack trace, and exit the program immediately.

neco_stream_make()

int neco_stream_make(neco_stream **stream, int fd);

neco_stream_make_buffered()

int neco_stream_make_buffered(neco_stream **stream, int fd);

neco_stream_close()

int neco_stream_close(neco_stream *stream);

Close a stream.

neco_stream_close_dl()

int neco_stream_close_dl(neco_stream *stream, int64_t deadline);

Close a stream with a deadline. A deadline is provided to accomodate for buffered streams that may need to flush bytes on close

neco_stream_read()

ssize_t neco_stream_read(neco_stream *stream, void *data, size_t nbytes);

neco_stream_read_dl()

ssize_t neco_stream_read_dl(neco_stream *stream, void *data, size_t nbytes, int64_t deadline);

neco_stream_write()

ssize_t neco_stream_write(neco_stream *stream, const void *data, size_t nbytes);

neco_stream_write_dl()

ssize_t neco_stream_write_dl(neco_stream *stream, const void *data, size_t nbytes, int64_t deadline);

neco_stream_readfull()

ssize_t neco_stream_readfull(neco_stream *stream, void *data, size_t nbytes);

neco_stream_readfull_dl()

ssize_t neco_stream_readfull_dl(neco_stream *stream, void *data, size_t nbytes, int64_t deadline);

neco_stream_read_byte()

int neco_stream_read_byte(neco_stream *stream);

Read and returns a single byte. If no byte is available, returns an error.

neco_stream_read_byte_dl()

int neco_stream_read_byte_dl(neco_stream *stream, int64_t deadline);

Same as neco_stream_read_byte() but with a deadline parameter.

neco_stream_unread_byte()

int neco_stream_unread_byte(neco_stream *stream);

Unread the last byte. Only the most recently read byte can be unread.

neco_stream_flush()

int neco_stream_flush(neco_stream *stream);

Flush writes any buffered data to the underlying file descriptor.

neco_stream_flush_dl()

int neco_stream_flush_dl(neco_stream *stream, int64_t deadline);

Same as neco_stream_flush() but with a deadline parameter.

neco_stream_buffered_read_size()

ssize_t neco_stream_buffered_read_size(neco_stream *stream);

neco_stream_buffered_write_size()

ssize_t neco_stream_buffered_write_size(neco_stream *stream);

Generated with the help of doxygen