Landin library reference discuss source

Target scope is enforced by the module assertion below.

compiler.assert(compiler.os == linux or compiler.os == darwin
                or compiler.os == freebsd)

kqueue, the event interface Darwin and FreeBSD share, through their C libraries, raw: a failure is -1 with the reason in libc.errno_cell.

Every declaration is Darwin's and FreeBSD's. The module's scope is the hosted POSIX systems, as platform/linux's is, so a provider written once imports both, and on Linux it declares nothing (D481).

Name What it is
kqueue Make a queue. It is not inherited by a child fork makes; set libc.fd_cloexec with libc.fcntl so a program executed does not keep it.
kevent Make the changes given, then wait for events or a libc.timespec limit; none_given for no changes, no room or no limit. A change that fails comes back as an event with ev_error and the errno in its datum.
change A change record: an identifier, a filter, flags, filter flags and a datum.
event_record struct kevent: what is watched, the filter, the flags, the filter's flags, its datum and a word handed back with each event.
evfilt_read, evfilt_write, evfilt_signal, evfilt_proc, evfilt_timer Filters: readiness, a signal, a process's end with note_exit, a timer.
ev_add, ev_delete, ev_enable, ev_disable, ev_oneshot, ev_clear Change flags.
ev_eof, ev_error Reported flags.
note_seconds, note_useconds, note_nseconds, and FreeBSD's note_mseconds A timer's unit; with none it is milliseconds.

A signal the queue watches is recorded even when it is ignored or blocked, and is still delivered as its disposition says, so a program ignores or blocks it.

Fact Darwin FreeBSD
event_record 32 bytes 64 bytes, with four extension words
note_useconds, note_nseconds 2, 4 4, 8

Items

Values by target

Each name is declared once for each target condition below; a dash means the name is not declared there.

  • compiler.os == darwin: when compiler.os == darwin or compiler.os == freebsd; compiler.os == darwin
  • otherwise: when compiler.os == darwin or compiler.os == freebsd; not (compiler.os == darwin)
NameType compiler.os == darwin otherwise Documentation
note_msecondsu32 — 2 evfilt_timer filter flag: the period is in milliseconds, NOTE_MSECONDS, as with none.
note_nsecondsu32 4 8 evfilt_timer filter flag: the period is in nanoseconds, NOTE_NSECONDS. FreeBSD / Darwin numbers it otherwise.
note_secondsu32 1 1 evfilt_timer filter flag: the period is in seconds, NOTE_SECONDS; with none of these it is in milliseconds.
note_usecondsu32 2 4 evfilt_timer filter flag: the period is in microseconds, NOTE_USECONDS. FreeBSD / Darwin numbers it otherwise.

When compiler.os == darwin or compiler.os == freebsd

public changes_given: type = none_given | ptr event_record

The changes kevent makes first, or none_given.

event_record

type ↑ index

When compiler.os == darwin or compiler.os == freebsd; compiler.os == darwin platform/kqueue/kqueue.ldn:37

public event_record: type = layout(c) struct
    identifier: usize
    filter: i16
    flags: u16
    filter_flags: u32
    data: i64
    user_data: usize
end event_record

Darwin's struct kevent, 32 bytes: what is watched, as a descriptor, a signal's number, a process's ID or a timer's own number; the filter that watches it; the ev_* flags; the filter's own flags; the filter's datum, a count of bytes, of signals or of timer expiries, C's intptr_t, which is 64 bits there; and a word handed back unread with each event.

When compiler.os == darwin or compiler.os == freebsd; not (compiler.os == darwin) platform/kqueue/kqueue.ldn:59

public event_record: type = layout(c) struct
    identifier: usize
    filter: i16
    flags: u16
    filter_flags: u32
    data: i64
    user_data: usize
    extensions: [4]u64
end event_record

FreeBSD's struct kevent since FreeBSD 12, 64 bytes: Darwin's fields, the datum C's int64_t, and four words of filter extensions after them.

When compiler.os == darwin or compiler.os == freebsd

public events_filled: type = none_given | ptr mut event_record

Where kevent puts the events it reports, or none_given.

When compiler.os == darwin or compiler.os == freebsd

public wait_limit: type = none_given | ptr libc.timespec

How long kevent waits, or none_given to wait until an event comes. A zero time does not wait.

When compiler.os == darwin or compiler.os == freebsd

public none_given: atom

What kevent takes for no records and no time limit: C's null pointer.

When compiler.os == darwin or compiler.os == freebsd

public ev_add: u16 = 1

Flag: add the event, or change one added already, EV_ADD.

When compiler.os == darwin or compiler.os == freebsd

public ev_clear: u16 = 32

Flag: reset the event's state once it is reported, so that it is reported again only when something new happens, EV_CLEAR.

When compiler.os == darwin or compiler.os == freebsd

public ev_delete: u16 = 2

Flag: remove the event, EV_DELETE. An event on a descriptor is removed when the descriptor's last reference closes.

When compiler.os == darwin or compiler.os == freebsd

public ev_disable: u16 = 8

Flag: keep the event but do not report it, EV_DISABLE.

When compiler.os == darwin or compiler.os == freebsd

public ev_enable: u16 = 4

Flag: report the event when it happens, EV_ENABLE.

When compiler.os == darwin or compiler.os == freebsd

public ev_eof: u16 = 32768

Reported flag: a descriptor's end was reached, or its peer shut its side, EV_EOF; the filter flags may hold an errno.

When compiler.os == darwin or compiler.os == freebsd

public ev_error: u16 = 16384

Reported flag: a change could not be made, and the datum is its errno, EV_ERROR.

When compiler.os == darwin or compiler.os == freebsd

public ev_oneshot: u16 = 16

Flag: remove the event once it is reported, EV_ONESHOT.

When compiler.os == darwin or compiler.os == freebsd

public evfilt_proc: i16 = -5

Filter: something happened to a process, the identifier its ID, as the filter flags ask: note_exit for its end. EVFILT_PROC.

When compiler.os == darwin or compiler.os == freebsd

public evfilt_read: i16 = -1

Filter: a descriptor has data to read, or a listener a connection to accept; the datum is how much. EVFILT_READ, which the two systems number alike, as every filter and flag below.

When compiler.os == darwin or compiler.os == freebsd

public evfilt_signal: i16 = -6

Filter: a signal was sent to this process, the identifier its number; the datum counts the signals since the last report. The signal is recorded even when it is ignored or blocked, and is still delivered as its disposition says, so a program ignores or blocks each signal it watches. EVFILT_SIGNAL.

When compiler.os == darwin or compiler.os == freebsd

public evfilt_timer: i16 = -7

Filter: a timer, the identifier a number of the program's own, expiring after the datum's period in milliseconds unless a note_* unit says otherwise, and again after each period unless ev_oneshot is set. EVFILT_TIMER.

When compiler.os == darwin or compiler.os == freebsd

public evfilt_write: i16 = -2

Filter: a descriptor has room to write; the datum is how much. EVFILT_WRITE.

When compiler.os == darwin or compiler.os == freebsd

public note_exit: u32 = 2147483648

evfilt_proc filter flag: the process ended, NOTE_EXIT; the datum is its status as waitpid gives it.

When compiler.os == darwin or compiler.os == freebsd

public change:
    (identifier: usize, filter: i16, flags: u16, filter_flags: u32,
     data: i64) -> (record: event_record)

A change to what a queue watches: identifier with filter, and the flags, filter flags and datum it is made with, as the record kevent reads, with no user data.

When compiler.os == darwin or compiler.os == freebsd

public extern(c) kevent: (queue: i32, changes: changes_given,
                          change_count: i32, events: events_filled,
                          event_count: i32, limit: wait_limit)
                         -> (count: i32)

Make the change_count changes at changes to what queue watches, then wait until an event comes or limit passes and put at most event_count of them at events: how many it put there, zero when the limit passed first, or -1 with the reason in errno, EINTR when a signal came first. A change that fails is reported as an event with ev_error when there is room for it, and fails the call when there is none.

When compiler.os == darwin or compiler.os == freebsd

public extern(c) kqueue: () -> (descriptor: i32)

Make a kernel event queue: its descriptor, or -1 with the reason in errno. The descriptor is not inherited by a child fork makes, but an executed program keeps it unless libc.fd_cloexec is set on it with libc.fcntl.

Example program

This complete program is one of the compiler's runtime tests. View source.

The program, 132 lines
compiler/tests/fixtures/runtime/kqueue-events/main.ldn132 lines
import platform/kqueue
import platform/libc

--  platform/kqueue on Darwin and FreeBSD.  A queue watches a pipe's
--  reading end, reports nothing while the pipe is empty, reports it
--  readable with the bytes waiting and the word it was watched with once a
--  byte is written, and reports its end once the writing end is closed.
--  SIGUSR1, ignored, is sent to the process itself: the queue records it
--  all the same.  `sigprocmask` blocks SIGUSR2 and puts the mask back.  The
--  failure oracles: removing an event never added is reported as an event
--  with `ev_error` and ENOENT, adding an event for a closed descriptor as
--  one with EBADF, and a wait with nothing ready reports nothing.  Removing
--  one from a closed descriptor is no oracle: closing removed it, and
--  Darwin reports ENOENT where FreeBSD reports EBADF.

--  `struct kevent`: FreeBSD's carries four extension words.
on_darwin: bool = compiler.os == darwin

--  ENOENT and EBADF, the same on both systems.
no_entry: i64 = 2
bad_descriptor: i64 = 9

--  The word the pipe is watched with, handed back by each report.
pipe_word: usize = 0x0123456789abcdef

public main: () -> (code: i32) =
    code = 1
    record_size: usize = if on_darwin then 32 else 64 end if
    return when sizeof kqueue.event_record <> record_size

    code = 2
    queue: i32 = kqueue.kqueue()
    return when queue < 0
    return when libc.fcntl(queue, libc.f_setfd, libc.fd_cloexec) <> 0

    code = 3
    mut ends: [2]i32 = zeroed
    return when libc.pipe(addr ends[0]) <> 0
    mut watch_pipe: kqueue.event_record =
        kqueue.change(usize(ends[0]), kqueue.evfilt_read, kqueue.ev_add, 0,
                      0)
    watch_pipe.user_data = pipe_word
    mut reports: [4]kqueue.event_record = zeroed
    no_wait: libc.timespec = (seconds: 0, nanoseconds: 0)
    second: libc.timespec = (seconds: 1, nanoseconds: 0)
    --  Added, and nothing written: nothing ready, without waiting.
    return when kqueue.kevent(queue, addr watch_pipe, 1, addr reports[0], 4,
                              addr no_wait) <> 0

    --  A byte written: the reading end, its byte and its word.
    code = 4
    byte: [1]u8 = [33]
    return when libc.write(ends[1], addr byte[0], 1) <> 1
    return when kqueue.kevent(queue, kqueue.none_given, 0, addr reports[0],
                              4, addr second) <> 1
    first: kqueue.event_record = reports[0]
    return when first.filter <> kqueue.evfilt_read
      or first.identifier <> usize(ends[0]) or first.user_data <> pipe_word
    return when first.data <> 1
    mut taken: [1]u8 = zeroed
    return when libc.read(ends[0], addr taken[0], 1) <> 1

    --  The writing end closed: the reading end's end.
    code = 5
    return when libc.close(ends[1]) <> 0
    return when kqueue.kevent(queue, kqueue.none_given, 0, addr reports[0],
                              4, addr second) <> 1
    ended: kqueue.event_record = reports[0]
    return when (ended.flags & kqueue.ev_eof) == 0
      or ended.identifier <> usize(ends[0])

    --  Removing an event never added is an error event, ENOENT, and so is
    --  adding one for a descriptor once it is closed, EBADF, on both systems.
    code = 6
    never: kqueue.event_record =
        kqueue.change(usize(ends[0]), kqueue.evfilt_write, kqueue.ev_delete,
                      0, 0)
    return when kqueue.kevent(queue, addr never, 1, addr reports[0], 4,
                              addr no_wait) <> 1
    refused: kqueue.event_record = reports[0]
    return when (refused.flags & kqueue.ev_error) == 0
      or refused.data <> no_entry
    return when libc.close(ends[0]) <> 0
    addition: kqueue.event_record =
        kqueue.change(usize(ends[0]), kqueue.evfilt_read, kqueue.ev_add,
                      0, 0)
    return when kqueue.kevent(queue, addr addition, 1, addr reports[0], 4,
                              addr no_wait) <> 1
    closed: kqueue.event_record = reports[0]
    return when (closed.flags & kqueue.ev_error) == 0
      or closed.data <> bad_descriptor

    --  SIGUSR1 ignored, sent, and recorded.
    code = 7
    mut ignoring: libc.signal_action = zeroed
    ignoring.handler = libc.sig_ign
    return when libc.sigaction(libc.sigusr1, addr ignoring,
                               libc.no_address) <> 0
    watch_signal: kqueue.event_record =
        kqueue.change(usize(libc.sigusr1), kqueue.evfilt_signal,
                      kqueue.ev_add, 0, 0)
    return when kqueue.kevent(queue, addr watch_signal, 1, addr reports[0],
                              4, addr no_wait) <> 0
    return when libc.kill(libc.getpid(), libc.sigusr1) <> 0

    code = 8
    return when kqueue.kevent(queue, kqueue.none_given, 0, addr reports[0],
                              4, addr second) <> 1
    signalled: kqueue.event_record = reports[0]
    return when signalled.filter <> kqueue.evfilt_signal
      or signalled.identifier <> usize(libc.sigusr1) or signalled.data < 1
    mut restored: libc.signal_action = zeroed
    restored.handler = libc.sig_dfl
    return when libc.sigaction(libc.sigusr1, addr restored,
                               libc.no_address) <> 0

    --  SIGUSR2 blocked, and not blocked before; then the mask as it was.
    code = 9
    mut blocking: libc.signal_set = zeroed
    blocking.bits[0] = u32(1) << u32(libc.sigusr2 - 1)
    mut before: libc.signal_set = zeroed
    return when libc.sigprocmask(libc.sig_block, addr blocking,
                                 addr before) <> 0
    return when (before.bits[0] & blocking.bits[0]) <> 0
    mut during: libc.signal_set = zeroed
    return when libc.sigprocmask(libc.sig_setmask, addr before,
                                 addr during) <> 0
    return when (during.bits[0] & blocking.bits[0]) == 0

    return when libc.close(queue) <> 0
    code = 42
end main