?? RIGHT := 110 ??
?? NEWTITLE := 'NOS/VE Network Access: Socket Layer Internal Interface In 23D' ??
?? NEWTITLE := 'Global Declarations Referenced by This Module', EJECT ??
MODULE nlm$sk_await_socket_events;

{ PURPOSE:
{   This module contains the Socket Layer internal interface that provides the ability
{   to await events on more than one socket.
{ DESIGN:
{   This module contains code that has an execution bracket of 2, 3. It resides on
{   OSF$JOB_TEMPLATE_23D.

?? PUSH (LISTEXT := ON) ??
*copyc nac$null_connection_id
*copyc nae$sk_socket_layer
*copyc nat$sk_job_socket
*copyc nat$sk_socket_events
*copyc ost$free_running_clock
*copyc ost$status
?? POP ??
*copyc nap$condition_handler_trace
*copyc nap$namve_system_error
*copyc nlp$sk_await_socket_offer
*copyc nlp$sk_clear_job_socket_lock
*copyc nlp$sk_lock_job_socket
*copyc nlp$sk_remove_wait_socket_offer
*copyc nlp$sk_tcp_await_clear_to_send
*copyc nlp$sk_tcp_await_data_available
*copyc nlp$sk_tcp_check_accept_socket
*copyc nlp$sk_tcp_remove_accept_socket
*copyc nlp$sk_tcp_remove_clear_to_send
*copyc nlp$sk_tcp_remove_data_avail
*copyc nlp$sk_unlock_job_socket
*copyc nlp$udp_await_clear_to_send
*copyc nlp$udp_await_data_available
*copyc nlp$udp_remove_clear_to_send
*copyc nlp$udp_remove_data_available
*copyc osp$append_status_integer
*copyc osp$establish_condition_handler
*copyc osp$disestablish_cond_handler
*copyc osp$pop_inhibit_job_recovery
*copyc osp$push_inhibit_job_recovery
*copyc osp$set_status_abnormal
*copyc osp$set_status_condition
*copyc pmp$continue_to_cause
*copyc pmp$long_term_wait
?? OLDTITLE ??
?? NEWTITLE := '[XDCL, #GATE] nlp$sk_await_socket_events', EJECT ??
*copy nlh$sk_await_socket_events

  PROCEDURE [XDCL, #GATE] nlp$sk_await_socket_events
    (    socket_events: nat$sk_socket_events;
     VAR completed_events: nat$sk_socket_events;
     VAR count: integer;
     VAR status: ost$status);

    VAR
      activity_complete: boolean,
      current_time: ost$free_running_clock,
      i: integer,
      inhibit_job_recovery_pushed: boolean,
      job_socket: ^nat$sk_job_socket,
      null_events_specified: boolean,
      remaining_time: integer,
      start_time: ost$free_running_clock,
      wait: boolean;

?? NEWTITLE := 'terminate_await', EJECT ??

    PROCEDURE terminate_await
      (    condition: pmt$condition;
           condition_descriptor: ^pmt$condition_information;
           save_area: ^ost$stack_frame_save_area;
       VAR handler_status: ost$status);

      nap$condition_handler_trace (condition, save_area);
      CASE condition.selector OF
      = ifc$interactive_condition, jmc$job_resource_condition =
        pmp$continue_to_cause (pmc$execute_standard_procedure, handler_status);
        EXIT nlp$sk_await_socket_events;
      = pmc$block_exit_processing =
        remove_task_from_wait_lists (socket_events, completed_events, count);
        IF inhibit_job_recovery_pushed THEN
          osp$pop_inhibit_job_recovery;
          inhibit_job_recovery_pushed := FALSE;
          #SPOIL (inhibit_job_recovery_pushed);
        IFEND;
      ELSE
        pmp$continue_to_cause (pmc$execute_standard_procedure, handler_status);
      CASEND;

    PROCEND terminate_await;
?? OLDTITLE, EJECT ??

    start_time := #FREE_RUNNING_CLOCK (0);
    remaining_time := UPPERVALUE (ost$free_running_clock);
    status.normal := TRUE;
    null_events_specified := TRUE;
    count := 0;

    inhibit_job_recovery_pushed := FALSE;
    #SPOIL (inhibit_job_recovery_pushed);

    osp$establish_condition_handler (^terminate_await, {block_exit} TRUE);

{ Process socket events. Make the first pass to check for completed events without wait.
    wait := FALSE;

    REPEAT

    /await_events/
      FOR i := 1 TO UPPERBOUND (socket_events) DO
        CASE socket_events [i].event_kind OF
        = nac$sk_await_data_available =
          null_events_specified := FALSE;
          IF socket_events [i].socket_id > 0 THEN

            osp$push_inhibit_job_recovery;
            inhibit_job_recovery_pushed := TRUE;
            #SPOIL (inhibit_job_recovery_pushed);

            nlp$sk_lock_job_socket (socket_events [i].socket_id, job_socket);
            IF job_socket <> NIL THEN
              IF job_socket^.status = nac$sk_socket_open THEN
                IF job_socket^.socket_type = nac$sk_udp_socket THEN
                  nlp$udp_await_data_available (job_socket^.global_socket_id, wait, activity_complete);
                  IF activity_complete THEN
                    count := count + 1;
                    completed_events [count] := socket_events [i];
                    IF count >= UPPERBOUND (completed_events) THEN
                      nlp$sk_unlock_job_socket (socket_events [i].socket_id);
                      EXIT /await_events/;
                    IFEND;
                  IFEND;
                ELSEIF job_socket^.socket_type = nac$sk_tcp_socket THEN
                  IF (job_socket^.tcp_socket_type = nlc$tcp_connect_socket) OR
                        (job_socket^.tcp_socket_type = nlc$tcp_accept_socket) THEN
                    nlp$sk_tcp_await_data_available (job_socket^.connection_id, wait, activity_complete);
                    IF activity_complete THEN
                      count := count + 1;
                      completed_events [count] := socket_events [i];
                      IF count >= UPPERBOUND (completed_events) THEN
                        nlp$sk_unlock_job_socket (socket_events [i].socket_id);
                        EXIT /await_events/;
                      IFEND;
                    IFEND;
                  ELSEIF job_socket^.tcp_socket_type = nlc$tcp_listen_socket THEN
                    nlp$sk_tcp_check_accept_socket (job_socket^.application, job_socket^.port,
                          job_socket^.bound_address, wait, activity_complete);
                    IF activity_complete THEN
                      count := count + 1;
                      completed_events [count] := socket_events [i];
                      IF count >= UPPERBOUND (completed_events) THEN
                        nlp$sk_unlock_job_socket (socket_events [i].socket_id);
                        EXIT /await_events/;
                      IFEND;
                    IFEND;
                  ELSEIF job_socket^.tcp_socket_type = nlc$tcp_null_socket THEN
                    activity_complete := TRUE;
                    count := count + 1;
                    completed_events [count] := socket_events [i];
                    IF count >= UPPERBOUND (completed_events) THEN
                      nlp$sk_unlock_job_socket (socket_events [i].socket_id);
                      EXIT /await_events/;
                    IFEND;
                  IFEND;
                IFEND;
              ELSE { Socket closed or terminated
                count := count + 1;
                activity_complete := TRUE;
                completed_events [count] := socket_events [i];
                IF count >= UPPERBOUND (completed_events) THEN
                  nlp$sk_unlock_job_socket (socket_events [i].socket_id);
                  EXIT /await_events/;
                IFEND;
              IFEND;
              nlp$sk_unlock_job_socket (socket_events [i].socket_id);
            ELSE { Unknown socket
              nlp$sk_unlock_job_socket (socket_events [i].socket_id);
              osp$set_status_condition (nae$sk_unknown_socket, status);
              osp$append_status_integer (osc$status_parameter_delimiter, socket_events [i].socket_id, 10,
                    TRUE, status);
              EXIT /await_events/;
            IFEND;

            osp$pop_inhibit_job_recovery;
            inhibit_job_recovery_pushed := FALSE;
            #SPOIL (inhibit_job_recovery_pushed);

          ELSE { socket_events [i].socket_id = 0
            osp$set_status_condition (nae$sk_unknown_socket, status);
            osp$append_status_integer (osc$status_parameter_delimiter, socket_events [i].socket_id, 10, TRUE,
                  status);
            EXIT /await_events/;
          IFEND;

        = nac$sk_await_clear_to_send =
          null_events_specified := FALSE;
          IF socket_events [i].socket_id > 0 THEN

            osp$push_inhibit_job_recovery;
            inhibit_job_recovery_pushed := TRUE;
            #SPOIL (inhibit_job_recovery_pushed);

            nlp$sk_lock_job_socket (socket_events [i].socket_id, job_socket);
            IF job_socket <> NIL THEN
              IF job_socket^.status = nac$sk_socket_open THEN
                IF job_socket^.socket_type = nac$sk_udp_socket THEN
                  nlp$udp_await_clear_to_send (job_socket^.global_socket_id, wait, activity_complete);
                  IF activity_complete THEN
                    count := count + 1;
                    completed_events [count] := socket_events [i];
                    IF count >= UPPERBOUND (completed_events) THEN
                      nlp$sk_unlock_job_socket (socket_events [i].socket_id);
                      EXIT /await_events/;
                    IFEND;
                  IFEND;
                ELSEIF job_socket^.socket_type = nac$sk_tcp_socket THEN
                  IF (job_socket^.tcp_socket_type = nlc$tcp_connect_socket) OR
                        (job_socket^.tcp_socket_type = nlc$tcp_accept_socket) THEN
                    nlp$sk_tcp_await_clear_to_send (job_socket^.connection_id, wait, activity_complete);
                    IF activity_complete THEN
                      count := count + 1;
                      completed_events [count] := socket_events [i];
                      IF count >= UPPERBOUND (completed_events) THEN
                        nlp$sk_unlock_job_socket (socket_events [i].socket_id);
                        EXIT /await_events/;
                      IFEND;
                    IFEND;
                  ELSE { Either connect not done or a listen socket

{ Should it give a reject instead ???

                    activity_complete := TRUE;
                    count := count + 1;
                    completed_events [count] := socket_events [i];
                    IF count >= UPPERBOUND (completed_events) THEN
                      nlp$sk_unlock_job_socket (socket_events [i].socket_id);
                      EXIT /await_events/;
                    IFEND;
                  IFEND;
                IFEND;
              ELSE { Socket closed or terminated
                activity_complete := TRUE;
                count := count + 1;
                completed_events [count] := socket_events [i];
                IF count >= UPPERBOUND (completed_events) THEN
                  nlp$sk_unlock_job_socket (socket_events [i].socket_id);
                  EXIT /await_events/;
                IFEND;
              IFEND;
              nlp$sk_unlock_job_socket (socket_events [i].socket_id);
            ELSE { Unknown socket
              nlp$sk_unlock_job_socket (socket_events [i].socket_id);
              osp$set_status_condition (nae$sk_unknown_socket, status);
              osp$append_status_integer (osc$status_parameter_delimiter, socket_events [i].socket_id, 10,
                    TRUE, status);
              EXIT /await_events/;
            IFEND;

            osp$pop_inhibit_job_recovery;
            inhibit_job_recovery_pushed := FALSE;
            #SPOIL (inhibit_job_recovery_pushed);

          ELSE { socket_events [i].socket_id = 0
            osp$set_status_condition (nae$sk_unknown_socket, status);
            osp$append_status_integer (osc$status_parameter_delimiter, socket_events [i].socket_id, 10, TRUE,
                  status);
            EXIT /await_events/;
          IFEND;

        = nac$sk_await_socket_offer =
          null_events_specified := FALSE;

          osp$push_inhibit_job_recovery;
          inhibit_job_recovery_pushed := TRUE;
          #SPOIL (inhibit_job_recovery_pushed);

          nlp$sk_await_socket_offer (socket_events [i].source_job, wait, activity_complete);

          osp$pop_inhibit_job_recovery;
          inhibit_job_recovery_pushed := FALSE;
          #SPOIL (inhibit_job_recovery_pushed);

          IF activity_complete THEN
            count := count + 1;
            completed_events [count] := socket_events [i];
            IF count >= UPPERBOUND (completed_events) THEN
              EXIT /await_events/;
            IFEND;
          IFEND;

        = nac$sk_await_time =
          null_events_specified := FALSE;
          current_time := #FREE_RUNNING_CLOCK (0);
          remaining_time := start_time + socket_events [i].wait_time * 1000 - current_time;
          IF remaining_time <= 0 THEN
            count := count + 1;
            completed_events [count] := socket_events [i];
            IF count >= UPPERBOUND (completed_events) THEN
              EXIT /await_events/;
            IFEND;
          IFEND;
        = nac$sk_null_event =

        ELSE { Invalid event
          osp$set_status_condition (nae$sk_invalid_event, status);
          osp$append_status_integer (osc$status_parameter_delimiter, $INTEGER (socket_events [i].event_kind),
                10, TRUE, status);
          EXIT /await_events/;
        CASEND;
      FOREND /await_events/;

      IF inhibit_job_recovery_pushed THEN
        osp$pop_inhibit_job_recovery;
        inhibit_job_recovery_pushed := FALSE;
        #SPOIL (inhibit_job_recovery_pushed);
      IFEND;

      IF null_events_specified THEN
        osp$set_status_abnormal (nac$status_id, nae$sk_null_list, 'NAP$SK_AWAIT_SOCKET_EVENTS', status);
      IFEND;

      IF status.normal AND (count = 0) and wait THEN
        IF remaining_time > 0 THEN
          remaining_time := remaining_time DIV 1000;
          pmp$long_term_wait (remaining_time, remaining_time);
        IFEND;
      ELSE { wait = false

{ Make a second pass to queue the task on the wait lists.

        wait := TRUE;
      IFEND;
    UNTIL (NOT status.normal) OR (count > 0);

    IF wait THEN
      remove_task_from_wait_lists (socket_events, completed_events, count);
    IFEND;
    osp$disestablish_cond_handler;

  PROCEND nlp$sk_await_socket_events;
?? OLDTITLE ??
?? NEWTITLE := 'remove_task_from_wait_lists', EJECT ??

{ PURPOSE:
{   The purpose of this procedure is to remove the currently
{ executing task from the wait lists for the specified events.
{ For each socket event, the completed events list is searched
{ and if the event is incomplete, it is removed from the
{ corresponding wait list. The completed events are dequeued at
{ event completion time.

  PROCEDURE remove_task_from_wait_lists
    (    socket_events: nat$sk_socket_events;
         completed_events: nat$sk_socket_events;
         count: integer);

    VAR
      i: integer,
      inhibit_job_recovery_pushed: boolean,
      j: integer,
      job_socket: ^nat$sk_job_socket,
      socket_id:  0 .. nac$sk_max_socket_identifier;

?? NEWTITLE := 'terminate_remove', EJECT ??

    PROCEDURE terminate_remove
      (    condition: pmt$condition;
           condition_descriptor: ^pmt$condition_information;
           save_area: ^ost$stack_frame_save_area;
       VAR handler_status: ost$status);

      nap$condition_handler_trace (condition, save_area);
      CASE condition.selector OF
      = pmc$block_exit_processing =
        IF socket_id > 0 THEN
          nlp$sk_clear_job_socket_lock (socket_id);
        IFEND;
        IF inhibit_job_recovery_pushed THEN
          osp$pop_inhibit_job_recovery;
          inhibit_job_recovery_pushed := FALSE;
          #SPOIL (inhibit_job_recovery_pushed);
        IFEND;
      ELSE
        pmp$continue_to_cause (pmc$execute_standard_procedure, handler_status);
      CASEND;

    PROCEND terminate_remove;
?? OLDTITLE, EJECT ??

    socket_id := 0;
    inhibit_job_recovery_pushed := FALSE;
    #SPOIL (socket_id, inhibit_job_recovery_pushed);

    osp$establish_condition_handler (^terminate_remove, {block_exit} TRUE);

{ Remove the task from all the queues.

  /outer_loop/
    FOR i := 1 TO UPPERBOUND (socket_events) DO

{ Compare it with the completed events.

      FOR j := 1 TO count DO
        IF socket_events [i].event_kind = completed_events [j].event_kind THEN
          CASE socket_events [i].event_kind OF
          = nac$sk_await_data_available, nac$sk_await_clear_to_send =
            IF socket_events [i].socket_id = completed_events [j].socket_id THEN
              CYCLE /outer_loop/;
            IFEND;
          = nac$sk_await_socket_offer =
            IF socket_events [i].source_job = completed_events [j].source_job THEN
              CYCLE /outer_loop/;
            IFEND;
          ELSE
          CASEND;
        IFEND;
      FOREND;

{ Dequeue the task from the appropriate list.

      CASE socket_events [i].event_kind OF
      = nac$sk_await_data_available =
        socket_id := socket_events [i].socket_id;
        #SPOIL (socket_id);

        osp$push_inhibit_job_recovery;
        inhibit_job_recovery_pushed := TRUE;
        #SPOIL (inhibit_job_recovery_pushed);

        nlp$sk_lock_job_socket (socket_id, job_socket);
        IF job_socket <> NIL THEN
          IF job_socket^.status = nac$sk_socket_open THEN
            IF job_socket^.socket_type = nac$sk_udp_socket THEN
              nlp$udp_remove_data_available (job_socket^.global_socket_id);
            ELSEIF job_socket^.socket_type = nac$sk_tcp_socket THEN
              IF (job_socket^.tcp_socket_type = nlc$tcp_connect_socket) OR
                    (job_socket^.tcp_socket_type = nlc$tcp_accept_socket) THEN
                nlp$sk_tcp_remove_data_avail (job_socket^.connection_id);
              ELSEIF job_socket^.tcp_socket_type = nlc$tcp_listen_socket THEN
                nlp$sk_tcp_remove_accept_socket (job_socket^.application, job_socket^.port,
                      job_socket^.bound_address);
              IFEND;
            IFEND;
          IFEND;
        IFEND;
        nlp$sk_unlock_job_socket (socket_events [i].socket_id);
        socket_id := 0;
        #SPOIL (socket_id);

        osp$pop_inhibit_job_recovery;
        inhibit_job_recovery_pushed := FALSE;
        #SPOIL (inhibit_job_recovery_pushed);

      = nac$sk_await_clear_to_send =
        socket_id := socket_events [i].socket_id;
        #SPOIL (socket_id);

        osp$push_inhibit_job_recovery;
        inhibit_job_recovery_pushed := TRUE;
        #SPOIL (inhibit_job_recovery_pushed);

        nlp$sk_lock_job_socket (socket_id, job_socket);
        IF job_socket <> NIL THEN
          IF job_socket^.status = nac$sk_socket_open THEN
            IF job_socket^.socket_type = nac$sk_udp_socket THEN
              nlp$udp_remove_clear_to_send (job_socket^.global_socket_id);
            ELSEIF job_socket^.socket_type = nac$sk_tcp_socket THEN
              IF job_socket^.connection_id <> nac$null_connection_id THEN
                nlp$sk_tcp_remove_clear_to_send (job_socket^.connection_id);
              IFEND;
            IFEND;
          IFEND;
        IFEND;
        nlp$sk_unlock_job_socket (socket_events [i].socket_id);
        socket_id := 0;
        #SPOIL (socket_id);

        osp$pop_inhibit_job_recovery;
        inhibit_job_recovery_pushed := FALSE;
        #SPOIL (inhibit_job_recovery_pushed);

      = nac$sk_await_socket_offer =
        osp$push_inhibit_job_recovery;
        inhibit_job_recovery_pushed := TRUE;
        #SPOIL (inhibit_job_recovery_pushed);

        nlp$sk_remove_wait_socket_offer (socket_events [i].source_job);

        osp$pop_inhibit_job_recovery;
        inhibit_job_recovery_pushed := FALSE;
        #SPOIL (inhibit_job_recovery_pushed);


      = nac$sk_await_time =

{ Do nothing.

      = nac$sk_null_event =

      ELSE { Unknown event
        nap$namve_system_error ({Recoverable_error=} TRUE, 'Unknown event in event list.', NIL);
      CASEND;
    FOREND /outer_loop/;

    osp$disestablish_cond_handler;

    IF inhibit_job_recovery_pushed THEN
      osp$pop_inhibit_job_recovery;
      inhibit_job_recovery_pushed := FALSE;
      #SPOIL (inhibit_job_recovery_pushed);
    IFEND;

  PROCEND remove_task_from_wait_lists;
?? OLDTITLE ??
MODEND nlm$sk_await_socket_events;
