FRCプログラムが動く仕組み-Part3

Part1, Part2はこちら

おそらくこれが超過したときにはたらくやつ(Watchdog Class)
ただ、Interruptしてくるスレッドがわからない。


  private static void schedulerFunc() {
    m_queueMutex.lock();

    try {
      while (!Thread.currentThread().isInterrupted()) {
        /**
         * watchdogがenableのときと実質同じ
         * loopFuncのはじめでenableになり、最後でdisableになる
         */
        if (m_watchdogs.size() > 0) {
          /* 既定のループ時間(0.02秒)に達したか */
          boolean timedOut = !awaitUntil(m_schedulerWaiter, m_watchdogs.peek().m_expirationTime);
          if (timedOut) {
            if (m_watchdogs.size() == 0 || m_watchdogs.peek().m_expirationTime
                > RobotController.getFPGATime()) {
              /* 0.02秒以内にloopFunc()の処理が終わったら特になにもない。 */
              continue;
            }

            /* watchdogがタイムアウト */
            Watchdog watchdog = m_watchdogs.poll();

            long now = RobotController.getFPGATime();
            if (now  - watchdog.m_lastTimeoutPrintTime > kMinPrintPeriod) {
              watchdog.m_lastTimeoutPrintTime = now;
              if (!watchdog.m_suppressTimeoutMessage) {
                /* ラグすぎたりして、あまりにも一周が遅れるとき(1秒) これがコンソールに出る(見たことない人もいるかも?)*/
                System.out.format("Watchdog not fed within %.6fs\n", watchdog.m_timeout / 1.0e6);
              }
            }

            m_queueMutex.unlock();

           /** 
            * DriverStation.reportWarning("Loop time of " + m_period + "s overrun\n", false);と同値。
            * Loop時間をオーバーしたときによく見る
            */
            watchdog.m_callback.run();

            m_queueMutex.lock();
          }
        } else {
          while (m_watchdogs.size() == 0) {
            /**
             * watchdogがdisableのとき
             * enableになるまで待つ
             */
            m_schedulerWaiter.awaitUninterruptibly();
          }
        }
      }
    } finally {
      m_queueMutex.unlock();
    }
  }

lock(), unlock()は同じところを同時にいじらないための安全策。Interruptの件も同じ理由と思われる。
こちらを参照のこと。

既定時間に達したかを判定するときにexpirationTimeを引数としてとっているが、これはenableの処理を見ればわかると思う。


 /* loopFunc()の最初で呼ばれてた */
public void reset() {
    enable();
  }

  public void enable() {
    m_startTime = RobotController.getFPGATime();
    // モードの変化履歴などをリセット

    m_queueMutex.lock();
    try {
      /* ここで現在時間+規定の一周の時間(m_timeout, 0.02秒)を計算する */
      m_expirationTime = m_startTime + m_timeout;
      // 実質enableのマーカー追加
      // 待機中のスレッドをすべて開始
    } finally {
      m_queueMutex.unlock();
    }
  }

さて、以上のSchedulerFunc()はどこで呼ばれるのかというと、参照はここのみ。


public class Watchdog implements Closeable, Comparable {
  /* 略 */
  static {
    startDaemonThread(Watchdog::schedulerFunc);
  }
  /* 略 */
}

なんだこれ!?と思ったら、static Initializerとかいうらしい。
要はWatchdogにstaticなアクセスをしたときとかインスタンスを作ったときに呼ばれるもの。
とりあえずIterativeRobot(Robotの祖父的なもの)のコンストラクタでしか以上に当てはまる処理は行われていない。

そしてこのstartDaemonThread()は、


private static Thread startDaemonThread(Runnable target) {
    Thread inst = new Thread(target);
    // スレッドを優先度低めに
    inst.start();
    return inst;
  }

start()でschedulerFunc()が実行される。
これはinterrupされない限りwhileで回り続けるから、番犬として別スレッドで監視できる。

一周目に時間超過しやすいのはWatchdogクラスが別スレッドで開始とかされるからかな、とか思ったりした。

以上です。これを知って役立つことはあまりありません。
DriverStationに出てくる文字列の正体は分かるようにはなったと思います。
今後この記事を引用してくることがあるかもしれません。(エラーメッセージを読み解くときとか)

お疲れさまでした。