136fcf2017-05-24Stephen R. van den Berg //! Allows for interactive debugging and live data structure inspection //! in both single- and multi-threaded programs. //! Creates an independent background thread that every @[pollinterval] //! will show a list of running threads. //! Optionally, a @[triggersignal] can be specified which allows the dump to //! be triggered by a signal. //! //! Example: //! In the program you'd like to inspect, insert the following one-liner: //! @code
a8a66b2017-07-10Stephen R. van den Berg //! Debug.Inspect("/tmp/test.pike");
136fcf2017-05-24Stephen R. van den Berg //! @endcode //! Then start the program and keep it running. //! Next you create a /tmp/test.pike with the following content: //!@code //!void create() { //! werror("Only once per modification of test.pike\n"); //!} //! //!int main() { //! werror("This will run every iteration\n"); //! werror("By returning 1 here, we disable the stacktrace dumps\n"); //! return 0; //!} //!
a803872017-11-09Stephen R. van den Berg //!void _destruct() { //! werror("_destruct() runs just as often as create()\n");
136fcf2017-05-24Stephen R. van den Berg //!} //!@endcode //! Whenever you edit /tmp/test.pike, it will automatically reload //! the file. #pike __REAL_VERSION__ #pragma dynamic_dot #define POLLINTERVAL 4 //! Starts up the background thread. //! //! @param cb
012d372017-06-04Stephen R. van den Berg //! Specifies either the callback function which is invoked on each iteration,
136fcf2017-05-24Stephen R. van den Berg //! or the //! name of a file which contains a class which is (re)compiled automatically
012d372017-06-04Stephen R. van den Berg //! with an optional @expr{main()@} method, which will be called on each //! iteration. //! If the @expr{main()@} method returns 0, new stacktraces will be dumped //! every iteration; if it returns 1, stacktrace dumping will be suppressed.
136fcf2017-05-24Stephen R. van den Berg //! //! @note //! The compilation and the running of the callback is guarded by //! a catch(), so that failures (to compile) in that section will not //! interfere with the running program. //! //! @note //! If the list of running threads did not change, displaying the list again //! will be suppressed. //! //! @seealso
012d372017-06-04Stephen R. van den Berg //! @[triggersignal], @[pollinterval], @[_loopthread], @[_callback], //! @[Debug.globals]
136fcf2017-05-24Stephen R. van den Berg void create(string|function(void:void)|void cb) {
012d372017-06-04Stephen R. van den Berg  _callback = cb;
136fcf2017-05-24Stephen R. van den Berg  Thread.Thread(loop); } private Thread.Mutex running = Thread.Mutex(); private string lasttrace = ""; private object runit; private int lastmtime; private int skipnext; private int oldsignal;
a8a66b2017-07-10Stephen R. van den Berg //! If assigned to, it will allow the diagnostics inspection to be triggered
136fcf2017-05-24Stephen R. van den Berg //! by this signal. int triggersignal; //! The polling interval in seconds, defaults to 4. int pollinterval = POLLINTERVAL;
a8a66b2017-07-10Stephen R. van den Berg //! The inspect-thread. It will not appear in the displayed thread-list.
012d372017-06-04Stephen R. van den Berg Thread.Thread _loopthread;
136fcf2017-05-24Stephen R. van den Berg  //! Either the callback function which is invoked on each iteration, or the //! name of a file which contains a class which is (re)compiled automatically
012d372017-06-04Stephen R. van den Berg //! and called on each iteration. //! //! @seealso //! @[create] string|function(void:void) _callback;
136fcf2017-05-24Stephen R. van den Berg  private void loop(int sig) {
012d372017-06-04Stephen R. van den Berg  _loopthread = Thread.this_thread();
a8a66b2017-07-10Stephen R. van den Berg  for(;; inspect()) {
136fcf2017-05-24Stephen R. van den Berg  sleep(pollinterval, 1); if (triggersignal != oldsignal) {
a8a66b2017-07-10Stephen R. van den Berg  werror("\nTo inspect use: kill -%d %d\n\n",
136fcf2017-05-24Stephen R. van den Berg  triggersignal, getpid());
a8a66b2017-07-10Stephen R. van den Berg  signal(triggersignal, inspect);
136fcf2017-05-24Stephen R. van den Berg  } } } //! The internal function which does all the work each pollinterval.
188a9b2017-11-15Stephen R. van den Berg //! Run it directly to force a thread-dump. void inspect() {
136fcf2017-05-24Stephen R. van den Berg  Thread.MutexKey lock; if (lock = running->trylock(1)) { Thread.Thread thisthread = Thread.this_thread(); if (!skipnext) { int i = 0; String.Buffer buf = String.Buffer(); buf.add("\n{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{\n"); foreach(Thread.all_threads(); ; Thread.Thread ct)
012d372017-06-04Stephen R. van den Berg  if (ct != thisthread && ct != _loopthread)
136fcf2017-05-24Stephen R. van den Berg  buf.sprintf("====================================== Thread: %d\n%s\n", i++, describe_backtrace(ct.backtrace(), -1)); buf.add("}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}\n"); string s = buf.get(); if (s != lasttrace) // I shall say this only wence! werror(lasttrace = s); }
012d372017-06-04Stephen R. van den Berg  if (_callback) {
136fcf2017-05-24Stephen R. van den Berg  mixed err = catch {
012d372017-06-04Stephen R. van den Berg  if (stringp(_callback)) { Stdio.Stat st = file_stat(_callback);
136fcf2017-05-24Stephen R. van den Berg  if (st) { if (st->mtime > lastmtime) { lastmtime = st->mtime; runit = 0; // Explicitly, so that the destructor is called
012d372017-06-04Stephen R. van den Berg  runit = compile_file(_callback)();
136fcf2017-05-24Stephen R. van den Berg  skipnext = runit->main && runit->main(); } else if (runit && runit->main) skipnext = runit->main(); else skipnext = 1; } else runit = 0; } else
012d372017-06-04Stephen R. van den Berg  _callback();
136fcf2017-05-24Stephen R. van den Berg  }; if (err) werror("<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n" +describe_backtrace(err) +">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n"); }; } lock = 0; }