Synchronizing on a group of flags

An event flag group is an efficient and lightweight mechanism for synchronizing multiple threads, based on a 32bit value, in which each individual bit represents the state of a particular event defined by the application. Therefore, each group defines 32 individual events, from bit #0 to bit #31. Threads can wait for bits to be posted by other threads. Although EVL’s event flag group API is somewhat reminiscent of the eventfd interface, it is much simpler:

  • on the send side, posting the bits event mask to an event group is equivalent to performing atomically a bitwise OR operation such as group |= bits.

  • on the receive side, waiting for events means blocking until group becomes non-zero, at which point this value is returned to the thread heading the wait queue and the group is reset to zero in the same move. In other words, all bits set are immediately consumed by the first waiter and cleared in the group atomically. Threads wait on a group by priority order, which is determined by the scheduling policy they undergo.

Alt text

Unlike with the eventfd, there is no semaphore semantics associated to an event flag group, you may want to consider the EVL semaphore feature instead if this is what you are looking for.

Event flag group services


int evl_new_flags_any(struct evl_flags *flg, int clockfd, init initval, const char *fmt, ...)

This call creates a group of event flags, returning a file descriptor representing the new object upon success. This is the generic call form; for creating an event group with common pre-defined settings, see [evl_new_flags()}(#evl_new_flags).

  • flg

    An in-memory flag group descriptor is constructed by evl_new_flags_any(), which contains ancillary information other calls will need. flg is a pointer to such descriptor of type struct evl_flags.

  • clockfd

    Some flag group-related calls are timed like evl_timedwait_flags() which receives a timeout value. You can specify the EVL clock this timeout refers to by passing its file descriptor as clockfd. Built-in EVL clocks are accepted here.

  • initval

    The initial value of the flag group. You can use this parameter to pre-set some bits in the received event mask at creation time.

  • fmt

    A printf-like format string to generate the group name. A common way of generating unique names is to add the calling process’s pid somewhere into the format string as illustrated in the example. The generated name is used to form a last part of a pathname, referring to the new monitor element device underpinning the event group in the file system. So this name must contain only valid characters in this context, excluding slashes.

  • ...

    The optional variable argument list completing the format.

  • evl_new_flags_any() returns the file descriptor of the newly created flag group on success. Otherwise, a negated error code is returned:

    • -EEXIST The generated name is conflicting with an existing mutex, event, semaphore or flag group name.

    • -EINVAL Either clockfd does not refer to a valid EVL clock, or the generated group name is badly formed, likely containing invalid character(s), such as a slash. Keep in mind that it should be usable as a basename of a device element’s file path.

    • -ENAMETOOLONG The overall length of the device element’s file path including the generated name exceeds PATH_MAX.

    • -EMFILE The per-process limit on the number of open file descriptors has been reached.

    • -ENFILE The system-wide limit on the total number of open files has been reached.

    • -ENOMEM No memory available.

    • -ENXIO The EVL library is not initialized for the current process. Such initialization happens implicitly when evl_attach_self() is called by any thread of your process, or by explicitly calling evl_init(). You have to bootstrap the library services in a way or another before creating an EVL flag group.

    #include <evl/flags.h>
    
    static struct evl_flags flags;
    
    void create_new_flags(void)
    {
    	int fd;
    
    	fd = evl_new_flags_any(&flags, EVL_CLOCK_MONOTONIC, 0, "name_of_group");
    	/* skipping checks */
    	
    	return fd;
    }
    

    int evl_new_flags(struct evl_flags *flg, const char *fmt, ...)

    This call creates a zero-initialized group of event flags, timed on the built-in EVL monotonic clock. It is identical to calling:

    	evl_new_flags_any(flg, EVL_CLOCK_MONOTONIC, 0, fmt, ...);
    

    EVL_FLAGS_ANY_INITIALIZER(name, clockfd, initval)

    The static initializer you can use with flag groups. This is the generic form; for initializing a group of event flags with common pre-defined settings, see EVL_FLAGS_INITIALIZER.

  • name

    A name which is used to form a last part of a pathname, referring to the new monitor element device underpinning the event group in the file system. So this name must contain only valid characters in this context, excluding slashes.

  • clockfd

    The EVL clock associated with the group for timed operations. The descriptors of built-in EVL clocks are constant values which are accepted here.

  • initval

    The initial value of the event mask of the flag group.


  • EVL_FLAGS_INITIALIZER(name)

    This is a short-hand initializer specifying a zero init value and the built-in EVL monotonic clock for a group of event flags. It is identical to writing:

    EVL_FLAGS_ANY_INITIALIZER(name, EVL_CLOCK_MONOTONIC, 0);
    

    int evl_open_flags(struct evl_flags *flg, const char *fmt, ...)

    You can open an existing flag group, possibly from a different process, by calling evl_open_flags().

  • flg

    An in-memory flag group descriptor is constructed by evl_open_flags(), which contains ancillary information other calls will need. flg is a pointer to such descriptor of type struct evl_flags. The information is retrieved from the existing flag group which was opened.

  • fmt

    A printf-like format string to generate the name of the group to open. This name must exist in the EVL device hierachy at /dev/evl/monitor/.

  • ...

    The optional variable argument list completing the format.

  • evl_open_flags() returns the file descriptor referring to the opened group on success, Otherwise, a negated error code is returned:

    • -EINVAL The name refers to an existing object, but not to a group.

    • -EMFILE The per-process limit on the number of open file descriptors has been reached.

    • -ENFILE The system-wide limit on the total number of open files has been reached.

    • -ENOMEM No memory available.


    int evl_wait_flags(struct evl_flags *flg, int *r_bits)

    This service waits for events to be posted to the flag group. The caller is put to sleep by the core until this happens. Waiters are queued by order of scheduling priority.

    When the group receives some event(s), its value is passed back to the waiter leading the wait queue, then reset to zero before the latter returns.

    If the flag group value is non-zero on entry to this service, the caller returns immediately without blocking with the current set of posted flags.

  • flg

    The in-memory flag group descriptor constructed by either evl_new[_any]_flags() or evl_open_flags(), or statically built with EVL_FLAGS[_ANY]_INITIALIZER. In the latter case, an implicit call to evl_new_flags_any() is issued for flg before a wait is attempted, which may trigger a transition to the in-band execution mode for the caller.

  • r_bits

    The address of an integer which contains the set of flags received on successful return from the call.

  • evl_wait_flags() returns zero on success. Otherwise, a negated error code may be returned if:

    -EINVAL flg does not represent a valid in-memory flag group descriptor. If that pointer is out of the caller’s address space or points to read-only memory, the caller bluntly gets a memory access exception.

    If flg was statically initialized with EVL_FLAGS[_ANY]_INITIALIZER, then any error returned by evl_new_flags_any() may be passed back to the caller in case the implicit initialization call fails.


    int evl_timedwait_flags(struct evl_flags *flg, const struct timespec *timeout, int *r_bits)

    This call is a variant of evl_wait_flags() which allows specifying a timeout on the wait operation, so that the caller is unblocked after a specified delay sleeping without any flag being posted.

  • flg

    The in-memory flag group descriptor constructed by either evl_new[_any]_flags() or evl_open_flags(), or statically built with EVL_FLAGS[ANY]_INITIALIZER. In the latter case, an implicit call to evl_new_flags_any() for flg is issued before a wait is attempted, which may trigger a transition to the in-band execution mode for the caller.

  • timeout

    A time limit to wait for the group to be posted before the call returns on error. The clock mentioned in the call to evl_new_flags_any() will be used for tracking the elapsed time.

  • r_bits

    The address of an integer which contains the set of flags received on successful return from the call.

  • The possible return values include any status from evl_wait_flags(), plus:

    -ETIMEDOUT The timeout fired, after the amount of time specified by timeout.


    int evl_post_flags(struct evl_flags *flg, int bits)

    This call posts a (non-null) set of events to a flag group. The core adds the posted bits to the group’s current value using a bitwise OR operation. Afterwards, it unblocks the thread heading the wait queue of the flag group at the time of the call if any (i.e. having the highest priority among waiters).

  • flg

    The in-memory flag group descriptor constructed by either evl_new_flags[_any]() or evl_open_flags(), or statically built with EVL_FLAGS[_ANY]_INITIALIZER. In the latter case, an implicit call to evl_new_flags_any() for flg is issued before the event mask is posted, which may trigger a transition to the in-band execution mode for the caller.

  • bits

    The non-zero bitmask to add to the flag group value.

  • evl_post_flags() returns zero upon success. Otherwise, a negated error code is returned:

    -EINVAL flg does not represent a valid in-memory flag group descriptor. If that pointer is out of the caller’s address space or points to read-only memory, the caller bluntly gets a memory access exception.

    -EINVAL bits is zero.

    If flg was statically initialized with EVL_FLAGS[_ANY]_INITIALIZER but not passed to any flag group-related call yet, then any error status returned by evl_new_flags_any() may be passed back to the caller in case the implicit initialization call fails.


    int evl_trywait_flags(struct evl_flags *flg, int *r_bits)

    This call attempts to consume the event flags currently posted to a group without blocking the caller if there are none. If some events were pending at the time of the call, the group value is reset to zero before returning to the caller.

  • flg

    The in-memory flag group descriptor constructed by either evl_new_flags[_any]() or evl_open_flags(), or statically built with EVL_FLAGS[_ANY]_INITIALIZER. In the latter case, an implicit call to evl_new_flags_any() for flg is issued before a wait is attempted, which may trigger a transition to the in-band execution mode for the caller.

  • r_bits

    The address of an integer which contains the set of flags received on successful return from the call.

  • evl_trywait_flags() returns zero on success and the non-zero set of pending events. Otherwise, a negated error code may be returned if:

    -EAGAIN flg had no event pending at the time of the call.

    -EINVAL flg does not represent a valid in-memory flag group descriptor. If that pointer is out of the caller’s address space or points to read-only memory, the caller bluntly gets a memory access exception.

    If flg was statically initialized with EVL_FLAGS[_ANY]_INITIALIZER, then any error returned by evl_new_flags_any() may be passed back to the caller in case the implicit initialization call fails.


    int evl_peek_flags(struct evl_flags *flg, int *r_bits)

    This call is a variant of evl_trywait_flags() which does not consume the flags before returning to the caller. In other words, the group value is not reset to zero before returning a non-zero set of pending events, allowing the group value to be read multiple times with no side-effect.

  • flg

    The in-memory flag group descriptor constructed by either evl_new_flags[_any]() or evl_open_flags(), or statically built with EVL_FLAGS[_ANY]_INITIALIZER. In the latter case, the flag group becomes valid for a call to evl_peek_flags() only after a post or [try]wait operation was issued for it.

  • r_bits

    The address of an integer which contains the group value on successful return from the call.

  • evl_peek_flags() returns zero on success along with the current group value. Otherwise, a negated error code may be returned if:

    -EINVAL flg does not represent a valid in-memory flag group descriptor. If that pointer is out of the caller’s address space or points to read-only memory, the caller bluntly gets a memory access exception.


    int evl_close_flags(struct evl_flags *flg)

    You can use evl_close_flags() to dispose of an EVL flag group, releasing the associated file descriptor, at which point flg will not be valid for any subsequent operation from the current process. However, this flag group is kept alive in the EVL core until all file descriptors opened on it by call(s) to evl_open_flags() have been released, whether from the current process or any other process.

  • flg

    The in-memory descriptor of the flag group to dismantle.

  • evl_close_flags() returns zero upon success. Otherwise, a negated error code is returned:

    -EINVAL flg does not represent a valid in-memory flag group descriptor. If that pointer is out of the caller’s address space or points to read-only memory, the caller bluntly gets a memory access exception.

    Closing a statically initialized flag group descriptor which has never been used in wait or post operations always returns zero.


    Events pollable from an event flag group descriptor

    The evl_poll() interface can monitor the following events occurring on an event flag group descriptor:

    • POLLIN and POLLRDNORM are set whenever the flag group value is non-zero, which means that a subsequent attempt to read it by a call to evl_wait_flags(), evl_trywait_flags() or evl_timedwait_flags() might be successful without blocking (i.e. unless another thread sneaks in in the meantime and collects the pending flags).

    • POLLOUT and POLLWRNORM are set whenever the flag group value is zero, which means that no flag is pending at the time of the call. As a result, polling for such status waits for all pending events to have been read by the receiving side.