PreviousUpNext

15.3.477  src/lib/std/src/hostthread/io-wait-hostthread.api

# io-wait-hostthread.api
#
# Interface to a server hostthread designed to basically just
# sit in a loop doing C select() over and over on whatever
# file/pipe/socket descriptors are of current interest.
#
# Our main purpose is to offload I/O blocking from the
# main threadkit hostthread so that it can run full speed                               # Threadkit                     is from   src/lib/src/lib/thread-kit/src/core-thread-kit/threadkit.api
# while we sit around waiting for network packets to arrive.                    # threadkit                     is from   src/lib/src/lib/thread-kit/src/core-thread-kit/threadkit.pkg
#
# A secondary purpose of io_wait_hostthread is to provide the
# clock threadkit needs to drive its pre-emptive timeslicing.
# Reppy's original CML implementation uses SIGALRM for this,
# but this has two critical disadvantages:
#
#  o  SIGALRM is a bottleneck resource which everyone wants
#     to control.
#
#  o  Worse, SIGALRM interrupts "slow" system calls, causing
#     them to return EINTR instead of a useful result, requiring
#     the relevant C code to re-try.  We can ensure that our own
#     C support code does this, but one call -- connect() --
#     *cannot* be retried, and we cannot be sure that all C
#     libraries linked into the runtime will retry properly
#     on EINTR. (In fact, I'd bet good odds that some will not.)
#
# By having our select() time out regularly and generate
# a timeslice clock for threadkit based on those timeouts
# we almost completely avoid both of those problems.
#
# ("Almost" because, for example, SIGCHLD can still interrupt
# "slow" syscalls.  But that will usually be very very rare,
# since child process deaths are usually rare and for one
# to hit during a "slow" syscall lacking proper retry-on-EINTR
# will be doubly rare.)
#
# See also:
#
#     src/lib/std/src/hostthread/cpu-bound-task-hostthreads.api
#     src/lib/std/src/hostthread/io-bound-task-hostthreads.api

# Compiled by:
#     src/lib/std/standard.lib

stipulate
#   package hth =  hostthread;                                                  # hostthread                    is from   src/lib/std/src/hostthread.pkg
    package tim =  time;                                                        # time                          is from   src/lib/std/time.pkg
    package wio =  winix__premicrothread::io;                                   # winix__premicrothread::io     is from   src/lib/std/src/posix/winix-io--premicrothread.pkg
herein

    # This api is implemented in:
    #
    #     src/lib/std/src/hostthread/io-wait-hostthread.pkg
    #
    api Io_Wait_Hostthread {
        #
        is_running: Void -> Bool;                                               # Returns TRUE iff the server hostthread is running.
        #
        start_server_hostthread_if_not_running:      String -> Void;            # Clear the internal wait-request list and start the select() hostthread running.
                                                                                # This is a no-op if it is already running.
                                                                                # String arg is (only) used as human-readable caller ID when logging for debug purposes.
        Do_Stop
          =
          { per_who:    String,
            reply:      Void -> Void
          };
        stop_server_hostthread_if_running:     Do_Stop -> Void;

        Do_Echo
          =
          { what:  String,                                              # Primarily to test that the server is running.
            reply: String -> Void
          };
        echo:     Do_Echo -> Void;


        Do_Note_Iod_Reader
          =
          { io_descriptor:      wio::Iod,
            read_fn:            wio::Iod -> Void                # Call this closure ("function") on iod whenever input is available.
          };
        note_iod_reader: Do_Note_Iod_Reader -> Void;
        drop_iod_reader: wio::Iod -> Void;


        Do_Note_Iod_Writer
          =
          { io_descriptor:      wio::Iod,
            write_fn:           wio::Iod -> Void                # Call this closure ("function") on iod whenever input is available.
          };
        note_iod_writer: Do_Note_Iod_Writer -> Void;
        drop_iod_writer: wio::Iod -> Void;


        Do_Note_Iod_Oobder
          =
          { io_descriptor:      wio::Iod,
            oobd_fn:            wio::Iod -> Void                # Call this closure ("function") on iod whenever out-of-band-data ("oobd") is available.
          };
        note_iod_oobder: Do_Note_Iod_Oobder -> Void;
        drop_iod_oobder: wio::Iod -> Void;


# test: String -> Void;                                         # This is nominally temporary debug code; the String (only) identifies the caller for debug purposes.
                                                                # WE ASSUME ONLY ONE CALLER AT A TIME, SO WE DON'T WORRY ABOUT MUTUAL EXCLUSION.


        get_timeout_interval:  Void -> tim::Time;
        set_timeout_interval:  tim::Time -> Void;
            #
            # The main job of io-wait-hostthread.pkg is to sit
            # eternally in loop running  wio::wait_for_io_opportunity.
            # These two calls control the timeout for that call.

#       drop_per_loop_fn:       (Ref (Void -> Void)) -> Void;
#       note_per_loop_fn:       (Ref (Void -> Void)) -> Void;           # Call will be ignored if given refcell is already present in internal list.
            #
            # The io_wait_hostthread timeslicer uses us to generate
            # its (approximately) 50Hz timeslicing "clock". It uses
            #   
            #     note_per_loop_fn
            #   
            # to register a function with us to be called once
            # per outer loop (and consequently approximately once
            # per timeout interval).
            #
            # For generality we internally support a list of such
            # per-loop fns rather than a single such fn, all of which
            # get called once per outer loop.
            #
            # For flexibility we also support removing per-loop fns
            # from our internal list via
            #
            #     note_per_loop_fn
            #
            # (This might be needed if switching to an entirely
            # different threadkit scheduler, say.)
            #
            # To do this we must be able to compare list entries
            # for equality. Equality comparisons are not supported
            # for Void->Void values, but they are supported for
            # refcells -- this is the main reason we wrap per-loop fns
            # in refcells in these two calls.  (We never assign to
            # these refcells, although our client -- threadkit scheduler
            # -- could do so if it wished.)
            #
            # Note that if there is a lot of I/O going on, the
            # loop fn may be called much more frequently than the
            # timeout interval would suggest.  It is up to the
            # client to deal with this, if it is a problem.

        is_doing_useful_work:   Void -> Bool;
            #
            # This is support for
            #
            #     no_runnable_threads_left__fate
            # from
            #    src/lib/src/lib/thread-kit/src/glue/threadkit-base-for-os-g.pkg
            #
            # which is tasked with exit()ing if the system is
            # deadlocked -- which is to say, no thread ready
            # to run and provably no prospect of ever having
            # a thread ready to run.
            #
            # If we are listening on any fd, then we can eventually
            # wake up some thread when input arrives on that fd, so
            # the system is not provably deadlocked and
            # no_runnable_threads_left__fate() should not exit().
    };
end;

## Code by Jeff Prothero: Copyright (c) 2010-2015,
## released per terms of SMLNJ-COPYRIGHT.


Comments and suggestions to: bugs@mythryl.org

PreviousUpNext