?? RIGHT := 110 ??
?? NEWTITLE := 'NOS/VE job management queued file/job scheduler interfaces', ??
MODULE jmm$queue_file_sched_interfaces;

{ PURPOSE:
{   This module contains the interfaces used by the Queue File Management and
{   the Job Scheduler to perform their communication.

?? NEWTITLE := 'Global Declarations Referenced by this Module', EJECT ??
?? PUSH (LISTEXT := ON) ??
*copyc jmt$candidate_queued_jobs
*copyc jmt$kjl_application_state_set
*copyc jmt$kjl_index
*copyc jmt$job_class
*copyc oss$mainframe_pageable
*copyc ost$status
?? POP ??
*copyc jmp$job_selection_priority
*copyc jmp$set_examine_queue_event
*copyc osp$begin_system_activity
*copyc osp$clear_mainframe_sig_lock
*copyc osp$end_system_activity
*copyc osp$set_mainframe_sig_lock
*copyc pmp$get_compact_date_time
*copyc qfp$job_selection_priority
*copyc qfp$ready_job_leveler
*copyc qfp$relink_kjl_application
*copyc qfp$relink_kjl_client
*copyc qfp$relink_kjl_entry
*copyc qfp$relink_kjl_server
*copyc syp$wait
*copyc syp$system_is_idling
*copyc tmp$ready_system_task1
*copyc jmv$job_class_table_p
*copyc jmv$job_counts
*copyc jmv$job_scheduler_event
*copyc jmv$job_scheduler_table
*copyc jmv$kjl_p
*copyc jmv$kjlx_p
*copyc jmv$known_job_list
*copyc jmv$maximum_job_class_in_use
*copyc jmv$prevent_activation_of_jobs
*copyc jmv$sched_profile_is_loading
*copyc osv$mainframe_pageable_heap
*copyc qfv$kjl_lock
*copyc syv$recovering_job_count
?? OLDTITLE ??
?? NEWTITLE := 'Global Variables Declared by this Module', EJECT ??

  VAR
    jmv$candidate_queued_jobs: [XDCL, #GATE, oss$mainframe_pageable] jmt$candidate_queued_jobs,
    jmv$candidates_can_be_acquired: [XDCL, #GATE, oss$mainframe_pageable] boolean := TRUE,
    jmv$next_job_cand_refresh_time: [XDCL, #GATE, oss$mainframe_pageable] integer := 0,
    jmv$refresh_job_candidates: [XDCL, #GATE, oss$mainframe_pageable] boolean := TRUE;

?? OLDTITLE ??
?? NEWTITLE := '[XDCL] jmp$force_candidate_refresh' ??
*copy jmh$force_candidate_refresh

  PROCEDURE [XDCL] jmp$force_candidate_refresh
    (    flush_candidate_queue: boolean);

    jmv$candidates_can_be_acquired := NOT flush_candidate_queue;
    jmv$refresh_job_candidates := TRUE;
    jmp$set_examine_queue_event (jmc$examine_input_queue, jmc$null_job_class, { unconditional } TRUE);

{ Call osp$begin_system_activity to keep the task's priority high while the KJL lock
{ is clear.

    osp$begin_system_activity;
    osp$clear_mainframe_sig_lock (qfv$kjl_lock);

{ As soon as the variable jmv$refresh_job_candidates goes to FALSE it means that the job
{ scheduler has the KJL locked and is refreshing the candidate queue.  The next thing
{ is to lock the KJL to gain exclusive access.

    #SPOIL (jmv$refresh_job_candidates);
    WHILE jmv$refresh_job_candidates DO
      syp$wait ({milliseconds} 50);
      #SPOIL (jmv$refresh_job_candidates);
    WHILEND;
    osp$set_mainframe_sig_lock (qfv$kjl_lock);
    osp$end_system_activity;
    jmv$candidates_can_be_acquired := TRUE;
    jmv$refresh_job_candidates := TRUE;
    jmp$set_examine_queue_event (jmc$examine_input_queue, jmc$null_job_class, { unconditional } TRUE);
  PROCEND jmp$force_candidate_refresh;
?? OLDTITLE ??
?? NEWTITLE := '[XDCL] jmp$notify_job_scheduler_of_job', EJECT ??
*copy jmh$notify_job_scheduler_of_job

{ NOTES:
{   It is assumed that the Known Job List (KJL) is locked when this request is made.

  PROCEDURE [XDCL] jmp$notify_job_scheduler_of_job
    (    job_class: jmt$job_class;
         new_kjl_index: jmt$kjl_index);

    VAR
      current_time: jmt$clock_time,
      kjl_index: jmt$kjl_index;

{ Find the best initiation candidate in the job class.

    kjl_index := jmv$known_job_list.queued_class_entries [job_class].first_queued_class_entry;

    IF kjl_index <> jmc$kjl_undefined_index THEN

      IF jmv$candidate_queued_jobs [job_class].candidate_available THEN

{ If the current candidate is modified or terminated, notify the job scheduler
{ unconditionally.

        IF NOT assignable_job (jmv$candidate_queued_jobs [job_class].kjl_index) THEN
          jmv$refresh_job_candidates := TRUE;
          jmp$set_examine_queue_event (jmc$examine_input_queue, job_class, { unconditional } TRUE);
        ELSEIF jmv$candidate_queued_jobs [job_class].kjl_index <> kjl_index THEN
          IF new_kjl_index <> jmc$kjl_undefined_index THEN

{ If the current candidate is no longer the best candidate, notify the job scheduler.

            current_time := #FREE_RUNNING_CLOCK (0);
            IF jmp$job_selection_priority (current_time, new_kjl_index,
                  job_class) > jmp$job_selection_priority (current_time,
                  jmv$candidate_queued_jobs [job_class].kjl_index, job_class) THEN
              jmv$refresh_job_candidates := TRUE;
              jmp$set_examine_queue_event (jmc$examine_input_queue, job_class, { unconditional } FALSE);
            IFEND;
          IFEND;
        IFEND;
      ELSEIF within_class_limits (job_class) THEN

{ If a candidate can be initiated, notify the job scheduler.

      /search_for_candidate/
        WHILE kjl_index <> jmc$kjl_undefined_index DO
          IF eligible_job_categories (jmv$kjl_p^ [kjl_index].job_category_set) THEN
            jmv$refresh_job_candidates := TRUE;
            jmp$set_examine_queue_event (jmc$examine_input_queue, job_class, { unconditional } FALSE);
            EXIT /search_for_candidate/;
          IFEND;
          kjl_index := jmv$kjl_p^ [kjl_index].class_forward_link;
        WHILEND /search_for_candidate/;
      IFEND;
    IFEND;
  PROCEND jmp$notify_job_scheduler_of_job;
?? OLDTITLE ??
?? NEWTITLE := '[XDCL] jmp$notify_queued_files_job_end', EJECT ??
*copy jmh$notify_queued_files_job_end

  PROCEDURE [XDCL] jmp$notify_queued_files_job_end
    (    kjl_index: jmt$kjl_index);

    VAR
      job_class: jmt$job_class;

    osp$set_mainframe_sig_lock (qfv$kjl_lock);

    job_class := jmv$kjl_p^ [kjl_index].job_class;

    IF jmv$known_job_list.queued_class_entries [job_class].termination_count < jmc$maximum_job_count THEN
      jmv$known_job_list.queued_class_entries [job_class].termination_count :=
            jmv$known_job_list.queued_class_entries [job_class].termination_count + 1;
    IFEND;
    jmv$job_counts.initiated_jobs := jmv$job_counts.initiated_jobs - 1;
    jmv$job_counts.job_class_counts [job_class].completed_jobs :=
          jmv$job_counts.job_class_counts [job_class].completed_jobs + 1;
    jmv$job_counts.job_class_counts [job_class].initiated_jobs :=
          jmv$job_counts.job_class_counts [job_class].initiated_jobs - 1;
    IF jmv$kjlx_p^ [kjl_index].job_mode <> jmc$batch THEN
      jmv$job_counts.interactive_jobs := jmv$job_counts.interactive_jobs - 1;
    IFEND;

{ If the job says to restart, put it in as a queued job - If the jobs login family is not available then
{ defer the job - otherwise remove it from the KJL.

    IF jmv$kjlx_p^ [kjl_index].restart_job THEN
      qfp$relink_kjl_application (kjl_index, jmc$ve_input_application_index, jmc$kjl_application_new);
      qfp$relink_kjl_client (kjl_index, jmc$kjl_client_undefined);
      qfp$relink_kjl_entry (kjl_index, job_class, jmc$kjl_queued_entry);
      jmp$notify_job_scheduler_of_job (job_class, kjl_index);

    ELSEIF NOT jmv$kjl_p^ [kjl_index].login_family_available THEN
      qfp$relink_kjl_application (kjl_index, jmc$ve_input_application_index, jmc$kjl_application_unused);
      qfp$relink_kjl_client (kjl_index, jmc$kjl_client_undefined);
      qfp$relink_kjl_entry (kjl_index, job_class, jmc$kjl_deferred_entry);
      jmp$notify_job_scheduler_of_job (job_class, jmc$kjl_undefined_index);

    ELSE
      IF jmv$kjlx_p^ [kjl_index].system_label_p <> NIL THEN
        FREE jmv$kjlx_p^ [kjl_index].system_label_p IN osv$mainframe_pageable_heap^;
      IFEND;
      qfp$relink_kjl_client (kjl_index, jmc$kjl_client_undefined);
      qfp$relink_kjl_server (kjl_index, jmc$kjl_server_undefined);
      qfp$relink_kjl_application (kjl_index, jmc$ve_input_application_index, jmc$kjl_application_unused);
      qfp$relink_kjl_entry (kjl_index, job_class, jmc$kjl_unused_entry);
      jmp$notify_job_scheduler_of_job (job_class, jmc$kjl_undefined_index);
    IFEND;

    osp$clear_mainframe_sig_lock (qfv$kjl_lock);
  PROCEND jmp$notify_queued_files_job_end;
?? OLDTITLE ??
?? NEWTITLE := '[XDCL, #GATE] jmp$refresh_job_candidates', EJECT ??
*copy jmh$refresh_job_candidates

  PROCEDURE [XDCL, #GATE] jmp$refresh_job_candidates;

    VAR
      kjl_index: jmt$kjl_index,
      job_class: jmt$job_class;

    osp$set_mainframe_sig_lock (qfv$kjl_lock);
    jmv$refresh_job_candidates := FALSE;
    jmv$next_job_cand_refresh_time := UPPERVALUE (integer);

  /refresh_jobs_in_class/
    FOR job_class := jmc$system_job_class TO jmv$maximum_job_class_in_use DO
      IF jmv$candidate_queued_jobs [job_class].candidate_available THEN

{ Check if the available candidate job should be removed from the candidate queue.
{ Remove it if it is no longer a candidate or not the best one.

        IF (NOT within_class_limits (job_class)) OR (NOT eligible_job_categories
              (jmv$kjl_p^ [jmv$candidate_queued_jobs [job_class].kjl_index].job_category_set)) OR
              (NOT assignable_job (jmv$candidate_queued_jobs [job_class].kjl_index)) OR
              (special_circumstances ()) THEN

{ Save the kjl index of the current candidate.  The current candidate cannot be the
{ best candidate any longer.  So keep the current candidate's kjl index and find the
{ new best candidate if there is one.  Then, remove the current candidate from the
{ application and client threads.

          kjl_index := jmv$candidate_queued_jobs [job_class].kjl_index;
          refresh_job_in_class (job_class);

{ Relink the entry as "unacquired".

          qfp$relink_kjl_application (kjl_index, jmc$ve_input_application_index, jmc$kjl_application_new);
          qfp$relink_kjl_client (kjl_index, jmc$kjl_client_undefined);

{ If the current candidate is not the first on in the list, there may be a new best
{ candidate.

        ELSEIF NOT (jmv$candidate_queued_jobs [job_class].kjl_index =
              jmv$known_job_list.queued_class_entries [job_class].first_queued_class_entry) THEN

{ Relink the entry as "unacquired".

          qfp$relink_kjl_application (jmv$candidate_queued_jobs [job_class].kjl_index,
                jmc$ve_input_application_index, jmc$kjl_application_new);
          qfp$relink_kjl_client (jmv$candidate_queued_jobs [job_class].kjl_index, jmc$kjl_client_undefined);
          refresh_job_in_class (job_class);
        IFEND;
      ELSE
        refresh_job_in_class (job_class);
      IFEND;
    FOREND /refresh_jobs_in_class/;
    osp$clear_mainframe_sig_lock (qfv$kjl_lock);
  PROCEND jmp$refresh_job_candidates;
?? OLDTITLE ??
?? NEWTITLE := '[XDCL, #GATE] jmp$refresh_job_candidate_class', EJECT ??
*copy jmh$refresh_job_candidate_class

  PROCEDURE [XDCL, #GATE] jmp$refresh_job_candidate_class
    (    job_class: jmt$job_class;
         initiation_succeeded: boolean);

    VAR
      ignore_status: ost$status,
      kjl_index: jmt$kjl_index;

    osp$set_mainframe_sig_lock (qfv$kjl_lock);
    kjl_index := jmv$candidate_queued_jobs [job_class].kjl_index;

    IF initiation_succeeded THEN

{ Relink the entry as initiated so far as the scheduler application is concerned.

      qfp$relink_kjl_application (kjl_index, jmc$ve_input_application_index, jmc$kjl_application_initiated);
      jmv$kjlx_p^ [kjl_index].restart_job := FALSE;
      jmv$kjl_p^ [kjl_index].initiated_job_list_ordinal :=
            jmv$candidate_queued_jobs [job_class].initiated_job_list_ordinal;
      jmv$kjlx_p^ [kjl_index].job_monitor_global_task_id :=
            jmv$candidate_queued_jobs [job_class].job_monitor_global_task_id;
      pmp$get_compact_date_time (jmv$kjlx_p^ [kjl_index].job_initiation_time, ignore_status);
      jmv$job_counts.initiated_jobs := jmv$job_counts.initiated_jobs + 1;
      jmv$job_counts.job_class_counts [job_class].initiated_jobs := jmv$job_counts.
            job_class_counts [job_class].initiated_jobs + 1;

      IF jmv$kjlx_p^ [kjl_index].job_mode <> jmc$batch THEN
        jmv$job_counts.interactive_jobs := jmv$job_counts.interactive_jobs + 1;
      IFEND;

{ CAUTION: The relink of the entry must be done after ALL fields in the KJL are set - this
{          is because it is possible for a job to begin execution before the scheduler gets
{          here.  There is a point in JMP$INITIALIZE_JOB_ENVIRONMENT where the job will wait
{          for its KJL entry to become "initiated" before it goes on to a point where it will
{          have need to reference its KJL entry.

      qfp$relink_kjl_entry (kjl_index, job_class, jmc$kjl_initiated_entry);

      refresh_job_in_class (job_class);
    ELSE
      IF NOT assignable_job (kjl_index) THEN

        refresh_job_in_class (job_class);

{ Relink the job from the Scheduler application.

        qfp$relink_kjl_application (kjl_index, jmc$ve_input_application_index, jmc$kjl_application_new);
        qfp$relink_kjl_client (kjl_index, jmc$kjl_client_undefined);
      IFEND;
    IFEND;
    osp$clear_mainframe_sig_lock (qfv$kjl_lock);
  PROCEND jmp$refresh_job_candidate_class;
?? OLDTITLE ??
?? NEWTITLE := 'assignable_job', EJECT ??

{ PURPOSE:
{   The purpose of this function is to indicate if the job can be assigned as a
{ candidate for initiation.

  FUNCTION [INLINE] assignable_job
    (    kjl_index: jmt$kjl_index): boolean;

    assignable_job := (jmv$kjl_p^ [kjl_index].application_state IN
          -$jmt$kjl_application_state_set [jmc$kjl_application_terminated, jmc$kjl_application_modified]);
  FUNCEND assignable_job;
?? OLDTITLE ??
?? NEWTITLE := 'eligible_job_categories', EJECT ??

{ PURPOSE:
{   The purpose of this function is to indicate if the given categories would
{   allow a job to initiate on this mainframe.
{ DESIGN:
{   Check that the given categories contain all of the categories which are
{   required for initiation on this mainframe and none of the categories which
{   are excluded from initiation on this mainframe.

  FUNCTION [INLINE] eligible_job_categories
    (    categories: jmt$job_category_set): boolean;

    eligible_job_categories := ((jmv$job_scheduler_table.initiation_required_categories * categories) =
          jmv$job_scheduler_table.initiation_required_categories) AND
          ((jmv$job_scheduler_table.initiation_excluded_categories * categories) = $jmt$job_category_set []);

  FUNCEND eligible_job_categories;
?? OLDTITLE ??
?? NEWTITLE := 'refresh_job_in_class', EJECT ??

{ PURPOSE:
{   The purpose of this request is to refresh the job candidate queue for job scheduler. It is refreshed
{ when a place is open in the job class.  This request is made only via a request from the Job Scheduler.

  PROCEDURE refresh_job_in_class
    (    job_class: jmt$job_class);

    VAR
      candidate_for_this_mainframe: boolean,
      ignore_status_p: ^ost$status,
      job_priority: jmt$job_priority,
      kjl_index: jmt$kjl_index,
      ready_time: integer;

    jmv$candidate_queued_jobs [job_class].candidate_available := FALSE;
    IF within_class_limits (job_class) AND (NOT special_circumstances ()) THEN
      kjl_index := jmv$known_job_list.queued_class_entries [job_class].first_queued_class_entry;
      candidate_for_this_mainframe := FALSE;

{ Find the best candidate that is in the KJL for this job class.

      WHILE (kjl_index <> jmc$kjl_undefined_index) AND (NOT candidate_for_this_mainframe) DO
        candidate_for_this_mainframe := eligible_job_categories (jmv$kjl_p^ [kjl_index].job_category_set) AND
              (NOT jmv$known_job_list.queued_class_entries [job_class].class_blocked_for_initiation) AND
              (assignable_job (kjl_index));
        IF NOT candidate_for_this_mainframe THEN
          kjl_index := jmv$kjl_p^ [kjl_index].class_forward_link;
        IFEND;
      WHILEND;

{ If there is a job in the KJL or a job on a server mainframe then continue.
{ If the highest priority job is on the server, ready the job leveler task and
{ block the job class from initiation.  Otherwise, initiate the available job.

      IF candidate_for_this_mainframe OR (jmv$known_job_list.queued_class_entries [job_class].
            server_mainframe_priority > 0) THEN
        job_priority := qfp$job_selection_priority (#FREE_RUNNING_CLOCK (0), kjl_index);
        IF (jmv$known_job_list.queued_class_entries [job_class].number_of_jobs_needed = 0) AND
              (jmv$known_job_list.queued_class_entries [job_class].server_mainframe_priority >
              job_priority) THEN
          IF jmv$known_job_list.queued_class_entries [job_class].server_mainframe_priority <
                jmv$job_class_table_p^ [job_class].selection_priority.threshold THEN
            RETURN;
          IFEND;
          jmv$known_job_list.queued_class_entries [job_class].class_blocked_for_initiation := TRUE;
          qfp$ready_job_leveler;
        ELSEIF candidate_for_this_mainframe AND (job_priority >=
              jmv$job_class_table_p^ [job_class].selection_priority.threshold) THEN
          jmv$candidate_queued_jobs [job_class].candidate_available := TRUE;

{ NOTE: scheduler will setup the node when needed.

          jmv$candidate_queued_jobs [job_class].job_submission_time :=
                jmv$kjl_p^ [kjl_index].job_submission_time;
          jmv$candidate_queued_jobs [job_class].kjl_index := kjl_index;
          jmv$candidate_queued_jobs [job_class].system_supplied_name := jmv$kjl_p^ [kjl_index].
                system_job_name;
          jmv$candidate_queued_jobs [job_class].user_supplied_name := jmv$kjl_p^ [kjl_index].user_job_name;

{ Relink the entry to indicate that the Job Scheduler application has acquired the file.

          qfp$relink_kjl_client (kjl_index, jmc$kjl_client_this_mainframe);
          qfp$relink_kjl_application (kjl_index, jmc$ve_input_application_index,
                jmc$kjl_application_acquired);
        ELSEIF candidate_for_this_mainframe THEN

{ The best candidate job cannot initiate just yet.  Determine when it will be
{ able to initiate and make sure the scheduler will wake up in time to detect
{ it.

          IF (jmv$job_class_table_p^ [job_class].selection_priority.increment > 0) AND
                (jmv$job_class_table_p^ [job_class].initiation_age_interval <>
                jmc$unlimited_prio_age_interval) THEN
            ready_time := #FREE_RUNNING_CLOCK (0) + (jmv$job_class_table_p^ [job_class].selection_priority.
                  threshold - job_priority) * jmv$job_class_table_p^ [job_class].initiation_age_interval DIV
                  jmv$job_class_table_p^ [job_class].selection_priority.increment;
            IF ready_time < jmv$next_job_cand_refresh_time THEN
              jmv$next_job_cand_refresh_time := ready_time;
            IFEND;
          IFEND;
        IFEND;
      IFEND;
    IFEND;
  PROCEND refresh_job_in_class;
?? OLDTITLE ??
?? NEWTITLE := 'special_circumstances', EJECT ??

{ PURPOSE:
{   The purpose of this function is to indicate if the system is in a special state so that
{ jobs cannot be made available to the job scheduler.

  FUNCTION [INLINE] special_circumstances: boolean;

    special_circumstances := (syv$recovering_job_count > 0) OR syp$system_is_idling () OR
          jmv$sched_profile_is_loading OR (NOT jmv$candidates_can_be_acquired) OR
          jmv$prevent_activation_of_jobs;
  FUNCEND special_circumstances;
?? OLDTITLE ??
?? NEWTITLE := 'within_class_limits', EJECT ??

{ PURPOSE:
{   The purpose of this function is to indicate if the initiation of the job is permitted based on
{ the job class restrictions.

  FUNCTION [INLINE] within_class_limits
    (    job_class: jmt$job_class): boolean;

    within_class_limits := ((jmv$job_class_table_p^ [job_class].initiation_level.preferred >
          jmv$job_counts.job_class_counts [job_class].initiated_jobs) AND
          (jmv$job_class_table_p^ [job_class].enable_class_initiation) AND
          (jmv$job_class_table_p^ [job_class].required_categories *
          jmv$job_scheduler_table.initiation_excluded_categories = $jmt$job_category_set []) AND
          (jmv$job_class_table_p^ [job_class].excluded_categories *
          jmv$job_scheduler_table.initiation_required_categories = $jmt$job_category_set []));

  FUNCEND within_class_limits;
?? OLDTITLE ??
MODEND jmm$queue_file_sched_interfaces;
