From: Fred T. Hamster Date: Wed, 18 Feb 2026 21:49:14 +0000 (-0500) Subject: brought in the graceful shutdown support X-Git-Url: https://feistymeow.org/gitweb/?a=commitdiff_plain;h=19af88f2f3912d28e164043946bdfd43637bee22;p=feisty_meow.git brought in the graceful shutdown support 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. --- diff --git a/nucleus/library/processes/makefile b/nucleus/library/processes/makefile index 57e23f6e..b58fcf58 100644 --- a/nucleus/library/processes/makefile +++ b/nucleus/library/processes/makefile @@ -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 index 00000000..c0b7e145 --- /dev/null +++ b/nucleus/library/processes/process_anchor.cpp @@ -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 +#include +#include +#include +#include +#include +#include +#include + +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, 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 index 00000000..9dbab946 --- /dev/null +++ b/nucleus/library/processes/process_anchor.h @@ -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 +#include + +#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 index 00000000..ef21961d --- /dev/null +++ b/nucleus/library/processes/shutdown_alerter.cpp @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +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 index 00000000..3869bcfb --- /dev/null +++ b/nucleus/library/processes/shutdown_alerter.h @@ -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 +#include +#include + +// 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 index 00000000..b94df20c --- /dev/null +++ b/nucleus/library/tests_processes/find_window.cpp @@ -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 +#include +#include +#include +#include +#include +#include +#include + +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 + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include +#endif // __BUILD_STATIC_APPLICATION__ + diff --git a/nucleus/library/tests_processes/makefile b/nucleus/library/tests_processes/makefile index f872ba24..e9bfb790 100644 --- a/nucleus/library/tests_processes/makefile +++ b/nucleus/library/tests_processes/makefile @@ -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 index 00000000..c937eda7 --- /dev/null +++ b/nucleus/library/tests_processes/makefile.shutdowner @@ -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 index 00000000..05d6d291 --- /dev/null +++ b/nucleus/library/tests_processes/t_shutdown_alerter.cpp @@ -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 +#include +#include +#include +#include + +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 index 00000000..592b4a2b --- /dev/null +++ b/nucleus/library/tests_processes/zing_window.cpp @@ -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 +#include +#include +#include +#include +#include +#include +#include + +#include + +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 + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include +#endif // __BUILD_STATIC_APPLICATION__ +