sourcepawn/jit/watchdog_timer.cpp
author David Anderson <dvander@alliedmods.net>
Wed Aug 14 23:54:25 2013 -0700 (2013-08-14)
changeset 4013 6201c1180e0e
child 4129 81d532b90c86
permissions -rw-r--r--
Implement a watchdog timer for scripts that take too long to execute (bug 5837, r=fyren).
dvander@4013
     1
// vim: set ts=8 sts=2 sw=2 tw=99 et:
dvander@4013
     2
//
dvander@4013
     3
// This file is part of SourcePawn.
dvander@4013
     4
// 
dvander@4013
     5
// SourcePawn is free software: you can redistribute it and/or modify
dvander@4013
     6
// it under the terms of the GNU General Public License as published by
dvander@4013
     7
// the Free Software Foundation, either version 3 of the License, or
dvander@4013
     8
// (at your option) any later version.
dvander@4013
     9
// 
dvander@4013
    10
// SourcePawn is distributed in the hope that it will be useful,
dvander@4013
    11
// but WITHOUT ANY WARRANTY; without even the implied warranty of
dvander@4013
    12
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
dvander@4013
    13
// GNU General Public License for more details.
dvander@4013
    14
// 
dvander@4013
    15
// You should have received a copy of the GNU General Public License
dvander@4013
    16
// along with SourcePawn.  If not, see <http://www.gnu.org/licenses/>.
dvander@4013
    17
#include "watchdog_timer.h"
dvander@4013
    18
#include "x86/jit_x86.h"
dvander@4013
    19
#include <string.h>
dvander@4013
    20
dvander@4013
    21
WatchdogTimer g_WatchdogTimer;
dvander@4013
    22
dvander@4013
    23
WatchdogTimer::WatchdogTimer()
dvander@4013
    24
  : terminate_(false),
dvander@4013
    25
    mainthread_(ke::GetCurrentThreadId()),
dvander@4013
    26
    last_frame_id_(0),
dvander@4013
    27
    second_timeout_(false),
dvander@4013
    28
    timedout_(false)
dvander@4013
    29
{
dvander@4013
    30
}
dvander@4013
    31
dvander@4013
    32
WatchdogTimer::~WatchdogTimer()
dvander@4013
    33
{
dvander@4013
    34
  Shutdown();
dvander@4013
    35
}
dvander@4013
    36
dvander@4013
    37
bool
dvander@4013
    38
WatchdogTimer::Initialize(size_t timeout_ms)
dvander@4013
    39
{
dvander@4013
    40
  if (thread_)
dvander@4013
    41
    return false;
dvander@4013
    42
dvander@4013
    43
  timeout_ms_ = timeout_ms;
dvander@4013
    44
dvander@4013
    45
  thread_ = new ke::Thread(this, "SM Watchdog");
dvander@4013
    46
  if (!thread_->Succeeded())
dvander@4013
    47
    return false;
dvander@4013
    48
dvander@4013
    49
  return true;
dvander@4013
    50
}
dvander@4013
    51
dvander@4013
    52
void
dvander@4013
    53
WatchdogTimer::Shutdown()
dvander@4013
    54
{
dvander@4013
    55
  if (terminate_ || !thread_)
dvander@4013
    56
    return;
dvander@4013
    57
dvander@4013
    58
  {
dvander@4013
    59
    ke::AutoLock lock(&cv_);
dvander@4013
    60
    terminate_ = true;
dvander@4013
    61
    cv_.Notify();
dvander@4013
    62
  }
dvander@4013
    63
  thread_->Join();
dvander@4013
    64
  thread_ = NULL;
dvander@4013
    65
}
dvander@4013
    66
dvander@4013
    67
void
dvander@4013
    68
WatchdogTimer::Run()
dvander@4013
    69
{
dvander@4013
    70
  ke::AutoLock lock(&cv_);
dvander@4013
    71
dvander@4013
    72
  // Initialize the frame id, so we don't have to wait longer on startup.
dvander@4013
    73
  last_frame_id_ = g_Jit.FrameId();
dvander@4013
    74
dvander@4013
    75
  while (!terminate_) {
dvander@4013
    76
    ke::WaitResult rv = cv_.Wait(timeout_ms_ / 2);
dvander@4013
    77
    if (terminate_)
dvander@4013
    78
      return;
dvander@4013
    79
dvander@4013
    80
    if (rv == ke::Wait_Error)
dvander@4013
    81
      return;
dvander@4013
    82
dvander@4013
    83
    if (rv != ke::Wait_Timeout)
dvander@4013
    84
      continue;
dvander@4013
    85
dvander@4013
    86
    // We reached a timeout. If the current frame is not equal to the last
dvander@4013
    87
    // frame, then we assume the server is still moving enough to process
dvander@4013
    88
    // frames. We also make sure JIT code is actually on the stack, since
dvander@4013
    89
    // our concept of frames won't move if JIT code is not running.
dvander@4013
    90
    //
dvander@4013
    91
    // Note that it's okay if these two race: it's just a heuristic, and
dvander@4013
    92
    // worst case, we'll miss something that might have timed out but
dvander@4013
    93
    // ended up resuming.
dvander@4013
    94
    uintptr_t frame_id = g_Jit.FrameId();
dvander@4013
    95
    if (frame_id != last_frame_id_ || !g_Jit.RunningCode()) {
dvander@4013
    96
      last_frame_id_ = frame_id;
dvander@4013
    97
      second_timeout_ = false;
dvander@4013
    98
      continue;
dvander@4013
    99
    }
dvander@4013
   100
dvander@4013
   101
    // We woke up with the same frame. We might be timing out. Wait another
dvander@4013
   102
    // time to see if so.
dvander@4013
   103
    if (!second_timeout_) {
dvander@4013
   104
      second_timeout_ = true;
dvander@4013
   105
      continue;
dvander@4013
   106
    }
dvander@4013
   107
dvander@4013
   108
    {
dvander@4013
   109
      // Prevent the JIT from linking or destroying runtimes and functions.
dvander@4013
   110
      ke::AutoLock lock(g_Jit.Mutex());
dvander@4013
   111
dvander@4013
   112
      // Set the timeout notification bit. If this is detected before any patched
dvander@4013
   113
      // JIT backedges are reached, the main thread will attempt to acquire the
dvander@4013
   114
      // monitor lock, and block until we call Wait().
dvander@4013
   115
      timedout_ = true;
dvander@4013
   116
    
dvander@4013
   117
      // Patch all jumps. This can race with the main thread's execution since
dvander@4013
   118
      // all code writes are 32-bit integer instruction operands, which are
dvander@4013
   119
      // guaranteed to be atomic on x86.
dvander@4013
   120
      g_Jit.PatchAllJumpsForTimeout();
dvander@4013
   121
    }
dvander@4013
   122
dvander@4013
   123
    // The JIT will be free to compile new functions while we wait, but it will
dvander@4013
   124
    // see the timeout bit set above and immediately bail out.
dvander@4013
   125
    cv_.Wait();
dvander@4013
   126
dvander@4013
   127
    second_timeout_ = false;
dvander@4013
   128
dvander@4013
   129
    // Reset the last frame ID to something unlikely to be chosen again soon.
dvander@4013
   130
    last_frame_id_--;
dvander@4013
   131
dvander@4013
   132
    // It's possible that Shutdown() raced with the timeout, and if so, we
dvander@4013
   133
    // must leave now to prevent a deadlock.
dvander@4013
   134
    if (terminate_)
dvander@4013
   135
      return;
dvander@4013
   136
  }
dvander@4013
   137
}
dvander@4013
   138
dvander@4013
   139
bool
dvander@4013
   140
WatchdogTimer::NotifyTimeoutReceived()
dvander@4013
   141
{
dvander@4013
   142
  // We are guaranteed that the watchdog thread is waiting for our
dvander@4013
   143
  // notification, and is therefore blocked. We take the JIT lock
dvander@4013
   144
  // anyway for sanity.
dvander@4013
   145
  {
dvander@4013
   146
    ke::AutoLock lock(g_Jit.Mutex());
dvander@4013
   147
    g_Jit.UnpatchAllJumpsFromTimeout();
dvander@4013
   148
  }
dvander@4013
   149
dvander@4013
   150
  timedout_ = false;
dvander@4013
   151
dvander@4013
   152
  // Wake up the watchdog thread, it's okay to keep processing now.
dvander@4013
   153
  ke::AutoLock lock(&cv_);
dvander@4013
   154
  cv_.Notify();
dvander@4013
   155
  return false;
dvander@4013
   156
}
dvander@4013
   157
dvander@4013
   158
bool
dvander@4013
   159
WatchdogTimer::HandleInterrupt()
dvander@4013
   160
{
dvander@4013
   161
  if (timedout_)
dvander@4013
   162
    return NotifyTimeoutReceived();
dvander@4013
   163
  return true;
dvander@4013
   164
}