]> feistymeow.org Git - feisty_meow.git/commitdiff
brought in the graceful shutdown support
authorFred T. Hamster <fred@feistymeow.org>
Wed, 18 Feb 2026 21:49:14 +0000 (16:49 -0500)
committerFred T. Hamster <fred@feistymeow.org>
Wed, 18 Feb 2026 21:49:14 +0000 (16:49 -0500)
this includes the process anchor and the shutdown alerter, and tests for those.
the tests are not currently compiling, but the library is up with the new classes.

nucleus/library/processes/makefile
nucleus/library/processes/process_anchor.cpp [new file with mode: 0644]
nucleus/library/processes/process_anchor.h [new file with mode: 0644]
nucleus/library/processes/shutdown_alerter.cpp [new file with mode: 0644]
nucleus/library/processes/shutdown_alerter.h [new file with mode: 0644]
nucleus/library/tests_processes/find_window.cpp [new file with mode: 0644]
nucleus/library/tests_processes/makefile
nucleus/library/tests_processes/makefile.shutdowner [new file with mode: 0644]
nucleus/library/tests_processes/t_shutdown_alerter.cpp [new file with mode: 0644]
nucleus/library/tests_processes/zing_window.cpp [new file with mode: 0644]

index 57e23f6e040e61f709242ae7bf82fe366ede37bc..b58fcf58623e3a434375ce45fa9c9c0d5d18aab2 100644 (file)
@@ -4,9 +4,9 @@ PROJECT = processes
 TYPE = library
 TARGETS = processes.lib
 SOURCE = configured_applications.cpp ethread.cpp heartbeat.cpp launch_process.cpp \
-  letter.cpp mailbox.cpp post_office.cpp \
-  process_control.cpp process_entry.cpp rendezvous.cpp safe_callback.cpp safe_roller.cpp \
-  state_machine.cpp thread_cabinet.cpp 
+  letter.cpp mailbox.cpp post_office.cpp process_anchor.cpp process_control.cpp \
+  process_entry.cpp rendezvous.cpp safe_callback.cpp shutdown_alerter.cpp \
+  safe_roller.cpp state_machine.cpp thread_cabinet.cpp 
 
 include cpp/rules.def
 
diff --git a/nucleus/library/processes/process_anchor.cpp b/nucleus/library/processes/process_anchor.cpp
new file mode 100644 (file)
index 0000000..c0b7e14
--- /dev/null
@@ -0,0 +1,360 @@
+/*
+*
+*  Name   : process_anchor
+*  Author : Chris Koeritz
+*
+****
+* Copyright (c) 2000-$now By Author.  This program is free software; you can
+* redistribute it and/or modify it under the terms of the GNU General Public
+* License as published by the Free Software Foundation; either version 2 of
+* the License or (at your option) any later version.  This is online at:
+*     http://www.fsf.org/copyleft/gpl.html
+* Please send any updates to: fred@gruntose.com
+*/
+
+#include "process_anchor.h"
+
+///#include <application/windoze_helper.h>
+#include <basis/array.h>
+#include <basis/definitions.h>
+#include <basis/utf_conversion.h>
+#include <basis/astring.h>
+#include <filesystem/filename.h>
+#include <loggers/program_wide_logger.h>
+#include <structures/static_memory_gremlin.h>
+
+using namespace application;
+using namespace basis;
+using namespace filesystem;
+using namespace loggers;
+using namespace structures;
+//using namespace timely;
+
+#undef LOG
+#define LOG(s) CLASS_EMERGENCY_LOG(program_wide_logger::get(), s)
+
+#ifdef __WIN32__
+const int STARTUP_MESSAGE = WM_USER + 123;
+  // the special message we send out on startup of the event loop.
+
+const int TIMER_EVENT_ID = 23;
+  // the ID we use to create a timer, if the user wants one.
+
+// an anchor_association tracks the object related to a window handle.
+struct anchor_association {
+  window_handle _handle;
+  process_anchor *_anchor_object;
+
+  anchor_association(window_handle wh = NULL_POINTER, process_anchor *wo = NULL_POINTER)
+      : _handle(wh), _anchor_object(wo) {}
+};
+
+class anchor_association_list
+: public array<anchor_association>, public virtual root_object
+{
+public:
+  DEFINE_CLASS_NAME("anchor_association_list");
+};
+
+// the associations list keeps track of anchors that have been created so
+// that the window procedure (on win32) can get to the real object.
+
+SAFE_STATIC(anchor_association_list, associations, )
+#endif //win32
+
+//////////////
+
+process_anchor::process_anchor()
+#ifdef __LINUX__
+: shutdown_alerter()
+#endif
+#ifdef __WIN32__
+: _instance(NULL_POINTER),
+  _anchor_title(new astring),
+  _anchor_class(new astring),
+  _class_reg(NULL_POINTER),
+  _wind_handle(NULL_POINTER),
+  _cycle(0),
+  _defunct(false)
+#endif
+{
+}
+
+process_anchor::~process_anchor()
+{
+  set_defunct();
+#ifdef __WIN32__
+  WHACK(_anchor_title);
+  WHACK(_anchor_class);
+  _class_reg = NULL_POINTER;
+  // remove the association for our anchor.
+  for (int i = 0; i < associations().length(); i++) {
+    if (associations()[i]._handle == _wind_handle) {
+      associations().zap(i, i);
+      break;
+    }
+  }
+#endif
+}
+
+void process_anchor::handle_startup() { /* nothing for base class. */ }
+
+void process_anchor::handle_timer() { /* nothing for base class. */ }
+
+void process_anchor::handle_shutdown() { /* nothing for base class. */ }
+
+bool process_anchor::close_this_program()
+{
+#ifdef __LINUX__
+  shutdown_alerter::close_this_program();
+  return true;
+#endif
+#ifdef __WIN32__
+  bool to_return = false;
+  for (int i = 0; i < associations().length(); i++) {
+    window_handle win = associations()[i]._handle;
+    int ret = PostMessage(win, WM_CLOSE, NULL_POINTER, NULL_POINTER);
+    // if we got a healthy return from any post, then we'll say this worked.
+    if (ret) to_return = true;
+  }
+  return to_return;
+#endif
+}
+
+bool process_anchor::defunct() const
+{
+#ifdef __LINUX__
+  return shutdown_alerter::is_defunct();
+#endif
+#ifdef __WIN32__
+  return _defunct;
+#endif
+}
+
+void process_anchor::set_defunct()
+{
+#ifdef __LINUX__
+  return shutdown_alerter::set_defunct();
+#endif
+#ifdef __WIN32__
+  _defunct = true;
+#endif
+}
+
+bool process_anchor::close_app_anchor(const astring &app_name)
+{
+  FUNCDEF("close_app_anchor");
+#ifdef __LINUX__
+  return shutdown_alerter::close_application(app_name);
+#endif
+#ifdef __WIN32__
+  astring title = process_anchor::make_well_known_title(app_name);
+#ifdef DEBUG_PROCESS_MANAGER
+  LOG(astring("title is: ") + title);
+#endif
+
+//hmmm: add support for linux...
+#ifdef __WIN32__
+  window_handle win_found = FindWindow(NULL_POINTER, to_unicode_temp(title));
+#ifdef DEBUG_PROCESS_MANAGER
+  LOG(astring(astring::SPRINTF, "found window %lx", win_found));
+#endif
+  if (!win_found) {
+    LOG(astring("Failed to find special window for [") + app_name
+      + astring("]"));
+    return false;
+  }
+
+//hmmm:
+//why would we find only one window if there were multiple apps under that
+//name?  how does windows deal with that?  is there a find window iterator?
+
+  int ret = PostMessage(win_found, WM_CLOSE, NULL_POINTER, NULL_POINTER);
+  if (!ret) {
+    LOG(astring("Failed to send close message to [") + app_name
+        + astring("]"));
+    return false;
+  }
+
+  LOG(astring("Sent close message to [") + app_name + astring("]"));
+#else
+  #ifdef DEBUG_PROCESS_MANAGER
+    LOG("graceful shutdown not implemented for this OS.");
+  #endif
+  return false;
+#endif
+  return true;
+#endif //win32
+}
+
+bool process_anchor::setup(application_instance instance,
+    const astring &app_name, int cycle)
+{
+#ifdef __LINUX__
+  if (instance) {}
+  return shutdown_alerter::setup(app_name, cycle);
+#endif
+#ifdef __WIN32__
+  if (_wind_handle) return true;  // already initialized.
+  // simple initializations first...
+  _instance = instance;
+  _cycle = cycle;
+  *_anchor_title = make_well_known_title(app_name);
+  *_anchor_class = make_well_known_class(app_name);
+  _class_reg = register_class();  // register a new window class for this.
+  _wind_handle = CreateWindow(to_unicode_temp(*_anchor_class),
+      to_unicode_temp(*_anchor_title), WS_OVERLAPPEDWINDOW, CW_USEDEFAULT,
+      0, CW_USEDEFAULT, 0, NULL_POINTER, NULL_POINTER, _instance, NULL_POINTER);
+  if (!_wind_handle) return false;
+
+  register_anchor(_wind_handle);  // hook in our anchor to the list.
+  ShowWindow(_wind_handle, SW_HIDE);
+  UpdateWindow(_wind_handle);
+  PostMessage(_wind_handle, STARTUP_MESSAGE, 0, 0);
+#endif  //win32
+  return true;
+}
+
+void process_anchor::register_anchor(window_handle wind)
+{
+#ifdef __WIN32__
+  // add the new anchor to our list of associations.
+  associations() += anchor_association(wind, this);
+#else
+  if (wind) {}
+#endif
+}
+
+#ifdef __WIN32__
+ATOM process_anchor::register_class()
+{
+  WNDCLASSEX wcex;
+
+  wcex.cbSize = sizeof(WNDCLASSEX);
+
+  wcex.style = CS_HREDRAW | CS_VREDRAW;
+  wcex.lpfnWndProc = (WNDPROC)WndProc;
+  wcex.cbClsExtra = 0;
+  wcex.cbWndExtra = 0;
+  wcex.hInstance = _instance;
+  wcex.hIcon = NULL_POINTER;
+  wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
+  wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
+  wcex.lpszMenuName = NULL_POINTER;
+  to_unicode_persist(hold_class, _anchor_class->s());
+  wcex.lpszClassName = hold_class;
+  wcex.hIconSm = NULL_POINTER;
+
+  return RegisterClassEx(&wcex);
+}
+#endif
+
+#ifdef __WIN32__
+LRESULT CALLBACK process_anchor::WndProc(HWND hWnd, UINT message,
+    WPARAM wParam, LPARAM lParam)
+{
+  switch (message) {
+    case STARTUP_MESSAGE: {
+      for (int i = 0; i < associations().length(); i++) {
+        if (associations()[i]._handle == hWnd) {
+          process_anchor &anch = *associations()[i]._anchor_object;
+          // invoke the initial callback so the anchor can initialize.
+          anch.handle_startup();
+
+          // set a timer going if they wanted one.
+          if (anch._cycle)
+            SetTimer(anch._wind_handle, TIMER_EVENT_ID, anch._cycle, 0);
+          break;
+        }
+      }
+      break;
+    }
+    case WM_CLOSE: {
+      for (int i = 0; i < associations().length(); i++) {
+        if (associations()[i]._handle == hWnd) {
+          // invoke the closing callback because we're leaving.
+          associations()[i]._anchor_object->handle_shutdown();
+          associations()[i]._anchor_object->set_defunct();
+          break;
+        }
+      }
+      return DefWindowProc(hWnd, message, wParam, lParam);
+      break;
+    }
+    case WM_DESTROY: {
+      PostQuitMessage(0);
+      break;
+    }
+    case WM_TIMER: {
+      bool found_window = false;  // records if we dispatched the event.
+      for (int i = 0; i < associations().length(); i++)
+        if (associations()[i]._handle == hWnd) {
+          // always stop the timer since we're suspending time while we're busy.
+          // more of a desire than a strategy.
+          KillTimer(hWnd, TIMER_EVENT_ID);
+          // invoke the timer callback to give the user some action.
+          associations()[i]._anchor_object->handle_timer();
+          found_window = true;
+          // always set the timer going again after handling it.
+          SetTimer(hWnd, TIMER_EVENT_ID,
+              associations()[i]._anchor_object->_cycle, 0);
+          break;
+        }
+      bool to_return = 0;
+      // if the timer wasn't for one of our anchors, we pass it to the default
+      // window procedure in hopes it will know what the hell to do with it.
+      if (!found_anchor)
+        to_return = DefWindowProc(hWnd, message, wParam, lParam);
+      return to_return;
+    }
+    case WM_PAINT: {
+      PAINTSTRUCT ps;
+      HDC hdc = BeginPaint(hWnd, &ps);
+      // add drawing code here if needed.
+      EndPaint(hWnd, &ps);
+      break;
+    }
+    default: return DefWindowProc(hWnd, message, wParam, lParam);
+  }
+  return 0;
+}
+#endif
+
+#ifdef __WIN32__
+astring process_anchor::make_well_known_title(const astring &application_name)
+{
+  filename app_short = application_name;
+  return astring("Anchor_for_") + app_short.basename();
+}
+#endif
+
+#ifdef __WIN32__
+astring process_anchor::make_well_known_class(const astring &application_name)
+{
+  filename app_short = application_name;
+  return astring("Dozeclass_for_") + app_short.basename();
+}
+#endif
+
+bool process_anchor::launch(process_anchor &win, application_instance handle,
+    const astring &application_name, int cycle)
+{
+  // prepare the anchor for its role...
+  if (!win.setup(handle, application_name, cycle)) return false;
+#ifdef __LINUX__
+  return shutdown_alerter::launch_console(win, application_name, cycle);
+#endif
+#ifdef __WIN32__
+  MSG msg;
+  msg.hwnd = 0; msg.message = 0; msg.wParam = 0; msg.lParam = 0;
+  while (GetMessage(&msg, NULL_POINTER, 0, 0)) {
+    TranslateMessage(&msg);
+    DispatchMessage(&msg);
+  }
+#endif
+  return true;
+}
+
+
+
+
diff --git a/nucleus/library/processes/process_anchor.h b/nucleus/library/processes/process_anchor.h
new file mode 100644 (file)
index 0000000..9dbab94
--- /dev/null
@@ -0,0 +1,144 @@
+#ifndef PROCESS_ANCHOR_CLASS
+#define PROCESS_ANCHOR_CLASS
+
+/*
+*
+*  Name   : process_anchor
+*  Author : Chris Koeritz
+*
+****
+* Copyright (c) 2000-$now By Author.  This program is free software; you can
+* redistribute it and/or modify it under the terms of the GNU General Public
+* License as published by the Free Software Foundation; either version 2 of
+* the License or (at your option) any later version.  This is online at:
+*     http://www.fsf.org/copyleft/gpl.html
+* Please send any updates to: fred@gruntose.com
+*/
+
+#include <application/windoze_helper.h>
+#include <basis/astring.h>
+
+#ifdef __LINUX__
+  #include "shutdown_alerter.h"
+#endif
+
+//! Implements a graceful shutdown procedure for an application.
+/*!
+  The advantage of the process_anchor (where it's actually a window, which is
+  to say, under win32) is that it's hidden and has a well-known name.  This
+  allows it to be found through FindWindow to facilitate a graceful shutdown
+  for the applications that use it.  On linux, it is implemented with a
+  shutdown_alerter object instead and is not implemented using windowing.
+*/
+class process_anchor
+#ifdef __LINUX__
+: public shutdown_alerter
+#endif
+{
+public:
+  process_anchor();
+    //!< constructor does very little; setup() begins operation.
+
+  virtual ~process_anchor();
+
+  DEFINE_CLASS_NAME("process_anchor");
+
+  static bool close_this_program();
+    //!< causes this particular program to shut down.
+    /*!< this method is available for programs that support the graceful
+    shutdown process.  it causes the machinery of the process_anchor to alert
+    the application that a graceful shutdown has been started.  the method
+    returns true if this program's process_anchor was found and sent the
+    close message. */
+
+  static bool close_app_anchor(const basis::astring &app_name);
+    //!< closes the anchor object associated with "app_name".
+    /*!< note that this closes *ALL* such anchors, so if the application had
+    more than one instance running, they will all shut down.
+also note: the above doesn't seem to be implemented in the current version.
+ */
+
+  bool defunct() const;
+    //!< returns true if the object has been marked as defunct.
+    /*!< this means that it is either shutting down or soon will be. */
+
+  void set_defunct();
+    //!< used by the derived class to mark that this object is about to exit.
+
+  bool setup(application_instance handle, const basis::astring &application_name,
+          int timing_cycle = 0);
+    //!< constructs a process_anchor for the "application_name" specified.
+    /*!< the instance "handle" is the link to the application.  the anchor is
+    created (and made hidden on windows) if successful.  the standard here for the
+    "application_name" is that it should not have any directory components,
+    but it should be the full program name (including extension).  if
+    "timing_cycle" is non-zero, then it is used as a timer interval.  every
+    time the interval elapses, the handle_timer() method is invoked.
+    note that this function is invoked by launch() and does not need to be
+    called by a user unless launch() is not being used to control the anchor's
+    lifetime. */
+
+  void register_anchor(window_handle anchor);
+    //!< this supports the anchor being created elsewhere.
+    /*!< when the object is created independently but should still function
+    as an process_anchor, then this method can be used to hook in that
+    external "window" into our support.  this only makes sense for the
+    windows version. */
+
+  static bool launch(process_anchor &anchor, application_instance handle,
+          const basis::astring &app, int cycle = 0);
+    //!< establishes a process_anchor for the program named "app".
+    /*!< on windows, this function will not return until WM_CLOSE is received; it is the
+    main message loop for a simple application.  the "handle" parameter should
+    be coming from the main function for the program.  note that the "window"
+    should be constructed but not have any functions called on it yet.  this
+    is important since this function does all the setup.  using a "window"
+    derived from process_anchor is also okay.  the "cycle" is passed to the
+    setup() method. */
+
+  virtual void handle_startup();
+    //!< derived classes can override this to catch the application startup.
+    /*!< this function is guaranteed to be called after the event processing
+    loop has started, but before much of anything else is done in the
+    application. */
+
+  virtual void handle_timer();
+    //!< invoked periodically if the anchor was setup() with a timer "cycle".
+
+  virtual void handle_shutdown();
+    //!< invoked just prior to the shutdown of this anchor.
+
+  static basis::astring make_well_known_title(const basis::astring &application_name);
+    //!< returns the string form of the well-known window title for the process_anchor.
+    /*!< this title identifies the "application_name" on ms-windows and is used for our
+    hidden window.  this is how the window can be found at runtime.
+    note that this approach will only work properly if there is only one
+    window by that name on a host at a time (due to how the windows are
+    registered). */
+  static basis::astring make_well_known_class(const basis::astring &application_name);
+    //!< same as above but for the anchor's class name.
+
+private:
+  application_instance _instance;  //!< the current instance in win32.
+  basis::astring *_anchor_title;  //!< the text for the title bar.
+  basis::astring *_anchor_class;  //!< the name for internal registration.
+  ATOM _class_reg;  //!< non-zero if the class has been registered.
+  window_handle _wind_handle;  //!< the handle for this window's object.
+  int _cycle;  //!< timer interval spacing out periodic activity.
+  bool _defunct;  //!< is the object shutting down?
+
+  ATOM register_class();
+    //!< sets up the window's class entry for hooking into ms-windows events.
+
+#ifdef __WIN32__
+  static LRESULT CALLBACK WndProc(HWND wind, UINT msg, WPARAM wp, LPARAM lp);
+    //!< the window procedure that handles all events.
+#endif
+
+  // not appropriate.
+  process_anchor(const process_anchor &);
+  process_anchor &operator =(const process_anchor &);
+};
+
+#endif // outer guard.
+
diff --git a/nucleus/library/processes/shutdown_alerter.cpp b/nucleus/library/processes/shutdown_alerter.cpp
new file mode 100644 (file)
index 0000000..ef21961
--- /dev/null
@@ -0,0 +1,227 @@
+
+
+
+/*****************************************************************************\
+*                                                                             *
+*  Name   : shutdown_alerter                                                  *
+*  Author : Chris Koeritz                                                     *
+*                                                                             *
+*******************************************************************************
+* Copyright (c) 2003-$now By Author.  This program is free software; you can  *
+* redistribute it and/or modify it under the terms of the GNU General Public  *
+* License as published by the Free Software Foundation; either version 2 of   *
+* the License or (at your option) any later version.  This is online at:      *
+*     http://www.fsf.org/copyleft/gpl.html                                    *
+* Please send any updates to: fred@gruntose.com                               *
+\*****************************************************************************/
+
+#include "process_control.h"
+#include "process_entry.h"
+#include "shutdown_alerter.h"
+
+#include <application/command_line.h>
+#include <basis/array.h>
+#include <basis/astring.h>
+#include <basis/mutex.h>
+#include <filesystem/filename.h>
+#include <loggers/program_wide_logger.h>
+#include <structures/set.h>
+#include <structures/static_memory_gremlin.h>
+#include <timely/time_control.h>
+#include <timely/time_stamp.h>
+#include <timely/timer_driver.h>
+
+#include <signal.h>
+#include <stdio.h>
+
+using namespace application;
+using namespace basis;
+using namespace filesystem;
+using namespace loggers;
+using namespace processes;
+using namespace structures;
+using namespace timely;
+
+#define DEBUG_SHUTDOWN_ALERTER
+  // uncomment for noisy version.
+
+#undef LOG
+#define LOG(s) CLASS_EMERGENCY_LOG(program_wide_logger::get(), s)
+
+//////////////
+
+SAFE_STATIC(astring, shutdown_alerter::_app_name, )
+
+bool &shutdown_alerter::_defunct() { static bool _defu = false; return _defu; }
+
+bool &shutdown_alerter::_saw_interrupt()
+{ static bool _saw = false; return _saw; }
+
+int &shutdown_alerter::_timer_period() { static int _tim = 0; return _tim; }
+
+//////////////
+
+static shutdown_alerter *_global_shutdown_alerter = NULL_POINTER;
+  // this static object is set by the setup() method.  it should only come
+  // into existence once during a program's lifetime.
+
+shutdown_alerter::shutdown_alerter()
+{
+}
+
+shutdown_alerter::~shutdown_alerter()
+{
+  set_defunct();
+  if (_global_shutdown_alerter) {
+    program_wide_timer().zap_timer(_global_shutdown_alerter);
+  }
+}
+
+void shutdown_alerter::handle_startup() { /* nothing for base class. */ }
+
+void shutdown_alerter::handle_shutdown() { /* nothing for base class. */ }
+
+void shutdown_alerter::handle_timer() { /* nothing for base class. */ }
+
+void shutdown_alerter::handle_timer_callback()
+{ 
+  // don't invoke the user's timer unless the object is still alive.
+  if (!is_defunct()) handle_timer();
+}
+
+void shutdown_alerter::close_this_program()
+{
+  set_defunct();
+}
+
+bool shutdown_alerter::close_application(const astring &app_name)
+{
+  FUNCDEF("close_application");
+  process_entry_array procs;
+  process_control querier;
+
+  // lookup process ids of apps.
+  bool got_list = querier.query_processes(procs);
+  if (!got_list) {
+    LOG("couldn't get process list.");
+    return false;
+  }
+  int_set pids;
+  got_list = querier.find_process_in_list(procs, app_name, pids);
+  if (!got_list) {
+    LOG("couldn't find process in the list of active ones.");
+    return true;
+  }
+
+  // zap all of them using our signal.
+  for (int i = 0; i < pids.length(); i++) {
+//would linux be better served with sigterm also?
+#ifdef __UNIX__
+    kill(pids[i], SIGHUP);
+#endif
+#ifdef __WIN32__
+//lame--goes to whole program.
+    raise(SIGTERM);
+#endif
+//hmmm: check results...
+  }
+
+  return true;
+}
+
+void shutdown_alerter::handle_OS_signal(int formal(sig_id))
+{
+  _saw_interrupt() = true;  // save the status.
+  if (_global_shutdown_alerter) {
+    _global_shutdown_alerter->close_this_program();
+  }
+}
+
+void shutdown_alerter::set_defunct()
+{
+  _defunct() = true;
+}
+
+bool shutdown_alerter::setup(const astring &app_name, int timer_period)
+{
+//hmmm: make sure not already initted.
+
+  // simple initializations first...
+  _timer_period() = timer_period;
+  _app_name() = app_name;
+
+  _global_shutdown_alerter = this;
+
+  // setup signal handler for HUP signal.  this is the one used to tell us
+  // to leave.
+#ifdef __UNIX__
+  signal(SIGHUP, handle_OS_signal);
+#endif
+
+  // setup a handler for interrupt (e.g. ctrl-C) also.
+  signal(SIGINT, handle_OS_signal);
+#ifdef __WIN32__
+  signal(SIGBREAK, handle_OS_signal);
+#endif
+
+  return true;
+}
+
+bool shutdown_alerter::launch_console(shutdown_alerter &alert,
+    const astring &app_name, int timer_period)
+{
+  FUNCDEF("launch_console");
+  if (!alert.setup(app_name, timer_period)) return false;
+
+  alert.handle_startup();  // tell the program it has started up.
+
+  // start a timer if they requested one.
+  if (_timer_period()) {
+    program_wide_timer().set_timer(_timer_period(), &alert);
+  }
+
+#ifdef DEBUG_SHUTDOWN_ALERTER
+  time_stamp next_report(10 * SECOND_ms);
+#endif
+
+  while (!alert.is_defunct()) {
+#ifdef DEBUG_SHUTDOWN_ALERTER
+    if (time_stamp() >= next_report) {
+      printf("%s: shout out from my main thread yo.\n", _global_argv[0]);
+      next_report.reset(10 * SECOND_ms);
+    }
+#endif
+    time_control::sleep_ms(42);
+  }
+  alert.handle_shutdown();
+  return true;
+}
+
+/*
+#ifdef __WIN32__
+bool shutdown_alerter::launch_event_loop(shutdown_alerter &alert,
+    const astring &app_name, int timer_period)
+{
+  if (!alert.setup(app_name, timer_period)) return false;
+  alert.handle_startup();
+
+  if (timer_period)
+    program_wide_timer().set_timer(timer_period, this);
+
+  MSG msg;
+  msg.hwnd = 0; msg.message = 0; msg.wParam = 0; msg.lParam = 0;
+  while (!alert.is_defunct() && (GetMessage(&msg, NULL_POINTER, 0, 0)) {
+    TranslateMessage(&msg);
+    DispatchMessage(&msg);
+  }
+  alert.handle_shutdown();
+
+  return true;
+}
+#endif
+*/
+
+
+
+
+
diff --git a/nucleus/library/processes/shutdown_alerter.h b/nucleus/library/processes/shutdown_alerter.h
new file mode 100644 (file)
index 0000000..3869bcf
--- /dev/null
@@ -0,0 +1,132 @@
+#ifndef SHUTDOWN_ALERTER_CLASS
+#define SHUTDOWN_ALERTER_CLASS
+
+/*****************************************************************************\
+*                                                                             *
+*  Name   : shutdown_alerter                                                  *
+*  Author : Chris Koeritz                                                     *
+*                                                                             *
+*******************************************************************************
+* Copyright (c) 2003-$now By Author.  This program is free software; you can  *
+* redistribute it and/or modify it under the terms of the GNU General Public  *
+* License as published by the Free Software Foundation; either version 2 of   *
+* the License or (at your option) any later version.  This is online at:      *
+*     http://www.fsf.org/copyleft/gpl.html                                    *
+* Please send any updates to: fred@gruntose.com                               *
+\*****************************************************************************/
+
+#include <basis/astring.h>
+#include <basis/definitions.h>
+#include <timely/timer_driver.h>
+
+// forward.
+//class basis::astring_object;
+
+//! A platform-independent way to alert a program that it should shut down immediately.
+/*!
+  This can provide a service management feature for graceful shutdown of an
+  application, allowing it a chance to close its objects cleanly, rather than
+  just being whacked in the middle of whatever it's doing.
+    Only one of these objects should be instantiated per program, but the
+  static methods can be used from anywhere in the program.
+*/
+class shutdown_alerter : public virtual basis::root_object, public timely::timeable
+{
+public:
+  shutdown_alerter();
+    //!< constructor does very little; setup() is what begins operation.
+
+  virtual ~shutdown_alerter();
+
+  DEFINE_CLASS_NAME("shutdown_alerter");
+
+  bool setup(const basis::astring &app_name, int timer_period = 0);
+    //!< constructs a shutdown_alerter for the "app_name" specified.
+    /*!< this can be any string, although it might be processed for certain
+    operating systems.  also, for close_this_program() to work properly, it
+    must be the application's basename.  the "timer_period" specifies how
+    frequently to invoke the handle_timer() method during runtime. */
+
+  static bool is_defunct() { return _defunct(); }
+    //!< returns true if the object has been marked as defunct.
+    /*!< this means that it is either shutting down or soon will be. */
+
+  static void set_defunct();
+    //!< used by the derived class to mark that this object is about to exit.
+    /*!< note that this can be used anywhere in the program to initiate an
+    exit of the program. */
+
+  inline bool saw_interrupt() { return _saw_interrupt(); }
+    //!< reports whether the process saw an interrupt from the user.
+
+  // these virtual methods can be overridden by applications derived from the
+  // shutdown_alerter.  they support a graceful shutdown process by which
+  // applications can be alerted that they must shutdown, allowing them to take
+  // care of releasing resources beforehand.
+
+  virtual void handle_startup();
+    //!< this function is called once the program has begun operation.
+
+  virtual void handle_shutdown();
+    //!< called during the program's shutdown process.
+    /*!< this is invoked just prior to the destruction of this class which is
+    also just before the shutdown of the program overall.  in this method,
+    the derived object must ensure that any threads the program started get
+    stopped, that any opened files get closed, and that any other resources
+    are released.  this is the application's last chance to clean up. */
+
+  virtual void handle_timer();
+    //!< called periodically if a timer period was specified.
+
+  // static methods that can be used by the program for starting up or for
+  // graceful shutdown.
+
+  static bool launch_console(shutdown_alerter &alert, const basis::astring &app_name,
+          int timer_period = 0);
+    //!< this is used to begin execution of a console mode application.
+    /*!< this method does not do anything except sit while the extant threads
+    are in play.  it will not return until the program must exit, as caused
+    by close_this_program() or close_application(). */
+
+#if 0  //not implemented.
+#ifdef __WIN32__
+  static bool launch_event_loop(shutdown_alerter &alert,
+          const basis::astring &app_name, int timer_period = 0);
+    //!< launches by starting up a windowing event loop.
+    /*!< this is appropriate for programs that are windowed and must
+    continually process window events. */
+#endif
+#endif
+
+  static void close_this_program();
+    //!< causes this particular application to begin shutting down.
+    /*!< this is a static method available for programs that support the
+    shutdown_alerter's graceful shutdown process.  it causes the application
+    to begin the shutdown. */
+
+  static bool close_application(const basis::astring &app_name);
+    //!< attempts to close the application named "app_name".
+    /*!< this can only be done if this program possesses sufficient rights to
+    zap that program. */
+
+  // internal methods not to be used by outside objects.
+
+  static void handle_OS_signal(int sig_id);
+    //!< processes the signal from the OS when its time to shut down.
+
+private:
+  static bool &_saw_interrupt();  //!< did we see a break from the user?
+  static basis::astring &_app_name();  //!< the of this application.
+  static bool &_defunct();  //!< is the program shutting down?
+  static int &_timer_period();  //!< rate at which timer goes off.
+
+  virtual void handle_timer_callback();
+    //!< invoked by the timer driver.
+
+  // not appropriate.
+  shutdown_alerter(const shutdown_alerter &);
+  shutdown_alerter &operator =(const shutdown_alerter &);
+};
+
+#endif // outer guard.
+
diff --git a/nucleus/library/tests_processes/find_window.cpp b/nucleus/library/tests_processes/find_window.cpp
new file mode 100644 (file)
index 0000000..b94df20
--- /dev/null
@@ -0,0 +1,135 @@
+/*****************************************************************************\
+*                                                                             *
+*  Name   : find_window                                                       *
+*  Author : Chris Koeritz                                                     *
+*                                                                             *
+*  Purpose:                                                                   *
+*                                                                             *
+*    Locates a window by the title.  If it's found, the window handle is      *
+*  displayed.                                                                 *
+*                                                                             *
+*******************************************************************************
+* Copyright (c) 2000-$now By Author.  This program is free software; you can  *
+* redistribute it and/or modify it under the terms of the GNU General Public  *
+* License as published by the Free Software Foundation; either version 2 of   *
+* the License or (at your option) any later version.  This is online at:      *
+*     http://www.fsf.org/copyleft/gpl.html                                    *
+* Please send any updates to: fred@gruntose.com                               *
+\*****************************************************************************/
+
+#include <basis/convert_utf.h>
+#include <basis/guards.h>
+#include <basis/istring.h>
+#include <basis/portable.h>
+#include <opsystem/command_line.h>
+#include <loggers/console_logger.h>
+#include <opsystem/filename.h>
+#include <data_struct/static_memory_gremlin.h>
+
+HOOPLE_STARTUP_CODE;
+
+window_handle matching_window = NIL;
+istring window_name_sought;
+
+BOOL CALLBACK zingers_enum_proc(window_handle hwnd, LPARAM lParam)
+{
+  const int MAX_TITLE = 1024;
+  flexichar win_name[MAX_TITLE + 8];  // add a little for good luck.
+  int chars = GetWindowText(hwnd, win_name, MAX_TITLE - 1);
+  if (chars > 0) {
+    // see if the current window matches what we're looking for.
+    if (istring(from_unicode_temp(win_name)).ifind(window_name_sought) >= 0) {
+      matching_window = hwnd;
+      return false;  // don't keep going.
+    }
+  }
+  return true;  // haven't found it yet.
+}
+
+int main(int argc, char *argv[])
+{
+  console_logger out;
+  command_line cmds(argc, argv);
+  if ( (cmds.entries() < 1)
+      || (cmds.get(0).type() != command_parameter::VALUE) ) {
+    out.log(cmds.program_name().basename().raw() + " usage:\n"
+        "this takes a single parameter, which is the name of a window\n"
+        "that is expected to be present in the current winstation. if the\n"
+        "window is found, its window handle is displayed.");
+    return 1;
+  }
+  istring title = cmds.get(0).text();
+  matching_window = NIL;  // reset our match first.
+  window_name_sought = title;
+  // enumerate the windows looking for a match.
+  EnumWindows(zingers_enum_proc, 0);
+  if (!matching_window) {
+    out.log("no matching window could be found.  ignoring request.");
+    return 1;
+  }
+//out.log("found matching window handle...");
+
+  if (matching_window)
+    out.log(isprintf("window handle is 0x%lx (or %ld) for ", matching_window,
+        matching_window) + title + ".");
+  else
+    out.log(istring("no window found for ") + title + ".");
+  return 0;
+}
+
+#ifdef __BUILD_STATIC_APPLICATION__
+  // static dependencies found by buildor_gen_deps.sh:
+  #include <basis/array.h>
+  #include <basis/byte_array.cpp>
+  #include <basis/callstack_tracker.cpp>
+  #include <basis/convert_utf.cpp>
+  #include <basis/definitions.cpp>
+  #include <basis/earth_time.cpp>
+  #include <basis/guards.cpp>
+  #include <basis/istring.cpp>
+  #include <basis/log_base.cpp>
+  #include <basis/memory_checker.cpp>
+  #include <basis/mutex.cpp>
+  #include <basis/object_base.h>
+  #include <basis/outcome.cpp>
+  #include <basis/packable.cpp>
+  #include <basis/portable.cpp>
+  #include <basis/sequence.h>
+  #include <basis/set.h>
+  #include <basis/trap_new.addin>
+  #include <basis/untrap_new.addin>
+  #include <basis/utility.cpp>
+  #include <basis/version_record.cpp>
+  #include <data_struct/amorph.h>
+  #include <data_struct/bit_vector.cpp>
+  #include <data_struct/byte_hasher.cpp>
+  #include <data_struct/configurator.cpp>
+  #include <data_struct/hash_table.h>
+  #include <data_struct/pointer_hash.h>
+  #include <data_struct/stack.h>
+  #include <data_struct/static_memory_gremlin.cpp>
+  #include <data_struct/string_hash.h>
+  #include <data_struct/string_hasher.cpp>
+  #include <data_struct/string_table.cpp>
+  #include <data_struct/symbol_table.h>
+  #include <data_struct/table_configurator.cpp>
+  #include <loggers/console_logger.cpp>
+  #include <loggers/file_logger.cpp>
+  #include <loggers/locked_logger.cpp>
+  #include <loggers/null_logger.cpp>
+  #include <loggers/program_wide_logger.cpp>
+  #include <opsystem/byte_filer.cpp>
+  #include <opsystem/command_line.cpp>
+  #include <opsystem/critical_events.cpp>
+  #include <opsystem/directory.cpp>
+  #include <opsystem/filename.cpp>
+  #include <opsystem/ini_config.cpp>
+  #include <opsystem/ini_parser.cpp>
+  #include <opsystem/path_configuration.cpp>
+  #include <opsystem/rendezvous.cpp>
+  #include <textual/byte_format.cpp>
+  #include <textual/parser_bits.cpp>
+  #include <textual/string_manipulation.cpp>
+  #include <textual/tokenizer.cpp>
+#endif // __BUILD_STATIC_APPLICATION__
+
index f872ba24d23ff8c097b2fdffc62e5f4974069a0c..e9bfb79087e3aa66ad054cb2171d649ce92e33ab 100644 (file)
@@ -5,11 +5,15 @@ include cpp/variables.def
 PROJECT = test_processes
 TYPE = test
 SOURCE = 
-TARGETS = test_safe_callback.exe test_state_machine.exe 
+TARGETS = find_window.exe test_safe_callback.exe test_state_machine.exe zing_window.exe
 LOCAL_LIBS_USED = unit_test application configuration filesystem loggers \
   nodes processes structures textual timely structures basis 
 #filesystem  mathematics 
 RUN_TARGETS = $(ACTUAL_TARGETS)
+LAST_TARGETS += build_shutdowner
 
 include cpp/rules.def
 
+build_shutdowner:
+       $(MAKE) -f makefile.shutdowner
+
diff --git a/nucleus/library/tests_processes/makefile.shutdowner b/nucleus/library/tests_processes/makefile.shutdowner
new file mode 100644 (file)
index 0000000..c937eda
--- /dev/null
@@ -0,0 +1,12 @@
+CONSOLE_MODE = t
+
+include cpp/variables.def
+
+PROJECT = t_shutdown_alerter
+TYPE = test
+TARGETS = t_shutdown_alerter.exe
+LOCAL_LIBS_USED = basis i_library
+RUN_TARGETS = 
+
+include cpp/rules.def
+
diff --git a/nucleus/library/tests_processes/t_shutdown_alerter.cpp b/nucleus/library/tests_processes/t_shutdown_alerter.cpp
new file mode 100644 (file)
index 0000000..05d6d29
--- /dev/null
@@ -0,0 +1,50 @@
+/*****************************************************************************\
+*                                                                             *
+*  Name   : t_shutdown_alerter                                                *
+*  Author : Chris Koeritz                                                     *
+*                                                                             *
+*  Purpose:                                                                   *
+*                                                                             *
+*    An example of using the shutdown_alerter object to manage the runtime    *
+*  of a program.                                                              *
+*                                                                             *
+*******************************************************************************
+* Copyright (c) 2005-$now By Author.  This program is free software; you can  *
+* redistribute it and/or modify it under the terms of the GNU General Public  *
+* License as published by the Free Software Foundation; either version 2 of   *
+* the License or (at your option) any later version.  This is online at:      *
+*     http://www.fsf.org/copyleft/gpl.html                                    *
+* Please send any updates to: fred@gruntose.com                               *
+\*****************************************************************************/
+
+#include <basis/istring.h>
+#include <basis/log_base.h>
+#include <opsystem/filename.h>
+#include <data_struct/static_memory_gremlin.h>
+#include <processes/shutdown_alerter.h>
+
+HOOPLE_STARTUP_CODE;
+
+#define BASE_LOG(s) STAMPED_EMERGENCY_LOG(program_wide_logger(), s)
+#define LOG(s) CLASS_EMERGENCY_LOG(program_wide_logger(), s)
+
+const int TIMING_CYCLE = 1408;
+  // how frequently timer should be hit.
+
+class my_anchor : public shutdown_alerter
+{
+public:
+  virtual void handle_startup() { BASE_LOG("into startup..."); }
+  virtual void handle_shutdown() { BASE_LOG("into shutdown..."); }
+  virtual void handle_timer() { BASE_LOG("into timer..."); }
+};
+
+int main(int formal(argc), char *formal(argv)[])
+{
+  my_anchor w;
+  BASE_LOG(isprintf("timer will hit every %d ms.", TIMING_CYCLE));
+  shutdown_alerter::launch_console(w,
+      filename(portable::application_name()).basename(), TIMING_CYCLE);
+  return 0;
+}
+
diff --git a/nucleus/library/tests_processes/zing_window.cpp b/nucleus/library/tests_processes/zing_window.cpp
new file mode 100644 (file)
index 0000000..592b4a2
--- /dev/null
@@ -0,0 +1,202 @@
+/*****************************************************************************\
+*                                                                             *
+*  Name   : zing_window                                                       *
+*  Author : Chris Koeritz                                                     *
+*                                                                             *
+*  Purpose:                                                                   *
+*                                                                             *
+*    Sends a message to a window.  The window can either be located by its    *
+*  unique window handle (if known) or by its exact title.  There is currently *
+*  no wildcard matching supported to find other types of titles.  If the      *
+*  window is located, then it is sent a windows message of your choice.  You  *
+*  can additionally add the wparam and lparam integers that get sent with     *
+*  the message.  This is useful for shutting windows down gracefully by       *
+*  sending them the WM_CLOSE message.                                         *
+*                                                                             *
+*******************************************************************************
+* Copyright (c) 2000-$now By Author.  This program is free software; you can  *
+* redistribute it and/or modify it under the terms of the GNU General Public  *
+* License as published by the Free Software Foundation; either version 2 of   *
+* the License or (at your option) any later version.  This is online at:      *
+*     http://www.fsf.org/copyleft/gpl.html                                    *
+* Please send any updates to: fred@gruntose.com                               *
+\*****************************************************************************/
+
+#include <basis/convert_utf.h>
+#include <basis/guards.h>
+#include <basis/istring.h>
+#include <basis/portable.h>
+#include <opsystem/command_line.h>
+#include <loggers/console_logger.h>
+#include <opsystem/filename.h>
+#include <data_struct/static_memory_gremlin.h>
+
+#include <stdio.h>
+
+HOOPLE_STARTUP_CODE;
+
+window_handle matching_window = NIL;
+istring window_name_sought;
+
+BOOL CALLBACK zingers_enum_proc(window_handle hwnd, LPARAM lParam)
+{
+  const int MAX_TITLE = 1024;
+  flexichar win_name[MAX_TITLE + 8];  // add a little for good luck.
+  int chars = GetWindowText(hwnd, win_name, MAX_TITLE - 1);
+  if (chars > 0) {
+    // see if the current window matches what we're looking for.
+    if (istring(from_unicode_temp(win_name)).ifind(window_name_sought) >= 0) {
+      matching_window = hwnd;
+      return false;  // don't keep going.
+    }
+  }
+  return true;  // haven't found it yet.
+}
+
+int main(int argc, char *argv[])
+{
+  console_logger out;
+  command_line cmds(argc, argv);
+  if ( (cmds.entries() < 2)
+      || (cmds.get(0).type() != command_parameter::VALUE) ) {
+    out.log(cmds.program_name().basename().raw() + " usage:\n"
+        "This takes at least two parameters.  The first parameter is a window\n"
+        "handle that is to be contacted.  Alternatively, the first parameter\n"
+        "can be the title of the window.  The second parameter is a numerical\n"
+        "event that the window is to be zinged with.  Zinging is synonymous\n"
+        "here with \"sending a message to the window\".  Depending on the\n"
+        "message chosen, the window will behave in various different ways.\n"
+        "If there are a third or fourth parameter, then these are taken as\n"
+        "the extra data to send in the PostMessage call for zinging.");
+    out.log(isprintf("\nExample windows message values:\n\t"
+        "WM_CLOSE = %d\n\tWM_PAINT = %d", WM_CLOSE, WM_PAINT));
+    return 1;
+  }
+
+  istring junk_text = cmds.get(0).text();
+  bool saw_hex_code = false;
+  if (junk_text.begins("0x")) {
+    // we have a hex code.  we will key off of that, which assumes that there
+    // is never going to be an important window whose title starts with that.
+    saw_hex_code = true;
+  }
+  int temp_handle = 0;
+  if (saw_hex_code) {
+    sscanf(junk_text.s(), "%lx", &temp_handle);
+  } else {
+    sscanf(junk_text.s(), "%ld", &temp_handle);
+  }
+
+//hmmm: this code is not so good for 64 bit.
+  #pragma warning(disable : 4312)
+  window_handle handle = (window_handle)temp_handle;
+
+  // we'll set the flag below if we find anything besides numbers in the
+  // window handle string.
+  bool has_alpha = false;
+  if (!saw_hex_code) {
+    // we only check for alphabetical characters if we didn't already
+    // decide the parameter had a hex code in front making it a number.
+    for (int i = 0; i < junk_text.length(); i++) {
+      if ( (junk_text[i] < '0') || (junk_text[i] > '9') ) {
+        has_alpha = true;
+        break;
+      }
+    }
+  }
+  if (has_alpha) {
+    // reset our match first.
+    matching_window = NIL;
+    window_name_sought = junk_text;
+//out.log("saw non-numbers in handle, trying as name.");
+    // enumerate the windows looking for a match.
+    EnumWindows(zingers_enum_proc, 0);
+    if (!matching_window) {
+      out.log("no matching window could be found.  ignoring request.");
+      return 1;
+    }
+//out.log("found matching window handle...");
+    handle = matching_window;
+  }
+
+  junk_text = cmds.get(1).text();
+  ULONG event = (ULONG)junk_text.convert(0);
+  ULONG p1 = 0;
+  ULONG p2 = 0;
+  if (cmds.entries() >= 3) {
+    junk_text = cmds.get(2).text();
+    p1 = (ULONG)junk_text.convert(0);
+  }
+  if (cmds.entries() >= 4) {
+    junk_text = cmds.get(3).text();
+    p2 = (ULONG)junk_text.convert(0);
+  }
+
+  if (!handle || !event) {
+    out.log("the window handle or the event was invalid.  ignoring request.");
+    return 1;
+  }
+
+  PostMessage(handle, event, p1, p2);
+
+  out.log(isprintf("posted at 0x%lx the zing (%u %u %u)", handle, event,
+      p1, p2));
+
+  return 0;
+}
+
+#ifdef __BUILD_STATIC_APPLICATION__
+  // static dependencies found by buildor_gen_deps.sh:
+  #include <basis/byte_array.cpp>
+  #include <basis/callstack_tracker.cpp>
+  #include <basis/convert_utf.cpp>
+  #include <basis/definitions.cpp>
+  #include <basis/earth_time.cpp>
+  #include <basis/guards.cpp>
+  #include <basis/istring.cpp>
+  #include <basis/log_base.cpp>
+  #include <basis/memory_checker.cpp>
+  #include <basis/mutex.cpp>
+  #include <basis/object_base.h>
+  #include <basis/outcome.cpp>
+  #include <basis/packable.cpp>
+  #include <basis/portable.cpp>
+  #include <basis/sequence.h>
+  #include <basis/set.h>
+  #include <basis/trap_new.addin>
+  #include <basis/untrap_new.addin>
+  #include <basis/utility.cpp>
+  #include <basis/version_record.cpp>
+  #include <data_struct/amorph.h>
+  #include <data_struct/bit_vector.cpp>
+  #include <data_struct/byte_hasher.cpp>
+  #include <data_struct/configurator.cpp>
+  #include <data_struct/hash_table.h>
+  #include <data_struct/pointer_hash.h>
+  #include <data_struct/stack.h>
+  #include <data_struct/static_memory_gremlin.cpp>
+  #include <data_struct/string_hash.h>
+  #include <data_struct/string_hasher.cpp>
+  #include <data_struct/string_table.cpp>
+  #include <data_struct/symbol_table.h>
+  #include <data_struct/table_configurator.cpp>
+  #include <loggers/console_logger.cpp>
+  #include <loggers/file_logger.cpp>
+  #include <loggers/locked_logger.cpp>
+  #include <loggers/null_logger.cpp>
+  #include <loggers/program_wide_logger.cpp>
+  #include <opsystem/byte_filer.cpp>
+  #include <opsystem/command_line.cpp>
+  #include <opsystem/critical_events.cpp>
+  #include <opsystem/directory.cpp>
+  #include <opsystem/filename.cpp>
+  #include <opsystem/ini_config.cpp>
+  #include <opsystem/ini_parser.cpp>
+  #include <opsystem/path_configuration.cpp>
+  #include <opsystem/rendezvous.cpp>
+  #include <textual/byte_format.cpp>
+  #include <textual/parser_bits.cpp>
+  #include <textual/string_manipulation.cpp>
+  #include <textual/tokenizer.cpp>
+#endif // __BUILD_STATIC_APPLICATION__
+