How to Delay Shutdown and Run a Process in Window Service

Run process during windows shutdown

The only way I could find to do it was: pre-create a child process suspended, and them un-suspend it on shutdown.

How to delay Topshelf service stopping during OS shutdown

It should be possible to register a custom EnvironmentBuilder (using extension method UseEnvironmentBuilder on a HostConfigurator instance) that returns a customized implementation of the HostEnvironment interface. Custom implementation of the HostEnvironment.CreateServiceHost method can return any implementation of Host you need (e.g. a Host that Run()s your ServiceBase).

You can reuse implementation of other HostEnvironment / Host methods by forwarding calls to WindowsHostEnvironment and WindowsServiceHost classes, respectively. WindowsHostEnvironment instance can be simply created in your custom EnvironmentBuilder constructor (and passed into custom HostEnvironment).

detect shutdown in window service

For a shutdown, override the OnShutdown method:

protected override void OnShutdown()
{
//your code here
base.OnShutdown();
}

For a logoff:

First, add an event handler to Microsoft.Win32.SystemEvents.SessionEnded in the Service Constructor:

public MyService()
{
InitializeComponent;
Microsoft.Win32.SystemEvents.SessionEnded += new Microsoft.Win32.SessionEndedEventHandler(SystemEvents_SessionEnded);
}

Then add the handler:

void SystemEvents_SessionEnded(object sender, Microsoft.Win32.SessionEndedEventArgs e)
{
//your code here
}

This should catch any ended session, including the console itself (the one running the services).

what is the maximum time windows service wait to process stop request and how to request for additional time

I wrote the following code to achieve it.

protected override void OnStop()
{
int timeout = 10000;
var task = Task.Factory.StartNew(() => MyTask());
while (!task.Wait(timeout))
{
RequestAdditionalTime(timeout);
}
}

The above code starts a Task in Parallel to the main thread (Task start running immediately), next line is to check if task is completed or not every 10 seconds and if it is not completed it requests additional 10 seconds and keep checking till task get completed.

Gracefully handling shutdown of a Windows service

After more research and some brainstorming I came to realise that the problems I've been experiencing were being caused by a very common design flaw regarding threads in Windows services.

The design flaw

Imagine you have a thread which does all your work. Your work consists of tasks that should be run again and again indefinitely. This is quite often implemented as follows:

volatile bool keepRunning = true;
Thread workerThread;

protected override void OnStart(string[] args)
{
workerThread = new Thread(() =>
{
while(keepRunning)
{
DoWork();
Thread.Sleep(10 * 60 * 1000); // Sleep for ten minutes
}
});
workerThread.Start();
}

protected override void OnStop()
{
keepRunning = false;
workerThread.Join();
// Ended gracefully
}

This is the very common design flaw I mentioned. The problem is that while this will compile and run as expected, you will eventually experience that your Windows service won't respond to commands from the service console in Windows. This is because your call to Thread.Sleep() blocks the thread, causing your service to become unresponsive. You will only experience this error if the thread blocks for longer than the timeout configured by Windows in HKLM\SYSTEM\CurrentControlSet\Control\WaitToKillServiceTimeout, because of this registry value this implementation may work for you if your thread is configured to sleep for a very short period of time and does it's work in an acceptable period of time.

The alternative

Instead of using Thread.Sleep() I decided to go for ManualResetEvent and System.Threading.Timer instead. The implementation looks something like this:

OnStart:

this._workerTimer = new Timer(new TimerCallback(this._worker.DoWork));
this._workerTimer.Change(0, Timeout.Infinite); // This tells the timer to perform the callback right now

Callback:

if (MyServiceBase.ShutdownEvent.WaitOne(0)) // My static ManualResetEvent
return; // Exit callback

// Perform lots of work here
ThisMethodDoesAnEnormousAmountOfWork();

(stateInfo as Timer).Change(_waitForSeconds * 1000, Timeout.Infinite); // This tells the timer to execute the callback after a specified period of time. This is the amount of time that was previously passed to Thread.Sleep()

OnStop:

MyServiceBase.ShutdownEvent.Set(); // This signals the callback to never ever perform any work again
this._workerTimer.Dispose(); // Dispose of the timer so that the callback is never ever called again

The conclusion

By implementing System.Threading.Timer and ManualResetEvent you will avoid your service becoming unresponsive to service console commands as a result of Thread.Sleep() blocking.

PS! You may not be out of the woods just yet!

However, I believe there are cases in which a callback is assigned so much work by the programmer that the service may become unresponsive to service console commands during workload execution. If that happens you may wish to look at alternative solutions, like checking your ManualResetEvent deeper in your code, or perhaps implementing CancellationTokenSource.

python console delay window shutdown

I think I've found a suitable solution:
I created my own small console application and hook into its message queue to catch the shutdown event.
I haven't tested it much yet and I also don't know if this is a good solution, but maybe it's helpful for someone.

First here is the code for my simple console based on tkinter. It shows stdout in black and stderr in red:

# a simple console based on tkinter to display stdout and stderr
class SimpleConsole(object):

def __init__(self, name):
self.root = Tk()
self.root.title(name)
self.init_ui()

def init_ui(self):
self.text_box = Text(self.root, wrap='word', height = 11, width=50)
self.text_box.grid(column=0, row=0, columnspan = 2, sticky='NSWE', padx=5, pady=5)
self.text_box.tag_config('std', foreground="black")
self.text_box.tag_config('err', foreground="red")
self.text_box.pack(side=LEFT, fill=BOTH, expand = YES)
self.text_box.yview()
self.yscrollbar = Scrollbar(self.root, orient=VERTICAL, command=self.text_box.yview)
self.yscrollbar.pack(side=RIGHT, fill=Y)
self.text_box["yscrollcommand"] = self.yscrollbar.set
sys.stdout = SimpleConsole.StdRedirector(self.text_box, "std")
sys.stderr = SimpleConsole.StdRedirector(self.text_box, "err")
self.update()

class StdRedirector(object):
def __init__(self, text_widget, tag):
self.text_space = text_widget
self.tag = tag

def write(self, string):
self.text_space.insert('end', string, self.tag)
self.text_space.see('end')

def flush(self):
pass

def update(self):
self.root.update()

def get_window_handle(self):
return int(self.root.wm_frame(), 16)

Then I created a class which hooks into the message queue of my console and manages the shutdown:

#class to handle a graceful shutdown by hooking into windows message queue
class GracefulShutdown:
def __init__(self, handle):
self.shutdown_requested = False
self._shutdown_functions = []
self.handle = handle

try:
if os.name == 'nt':

# Make a dictionary of message names to be used for printing below
self.msgdict = {}
for name in dir(win32con):
if name.startswith("WM_"):
value = getattr(win32con, name)
self.msgdict[value] = name

# Set the WndProc to our function
self.oldWndProc = win32gui.SetWindowLong(self.handle, win32con.GWL_WNDPROC, self.my_wnd_proc)
if self.oldWndProc == 0:
raise NameError("wndProc override failed!")

self.message_map = {win32con.WM_QUERYENDSESSION: self.hdl_query_end_session,
win32con.WM_ENDSESSION: self.hdl_end_session,
win32con.WM_QUIT: self.hdl_quit,
win32con.WM_DESTROY: self.hdl_destroy,
win32con.WM_CLOSE: self.hdl_close}

# pass a shutdown message to windows
retval = windll.user32.ShutdownBlockReasonCreate(self.handle,c_wchar_p("I'm still saving data!"))
if retval == 0:
raise NameError("shutdownBlockReasonCreate failed!")
except Exception as e:
logging.exception("something went wrong during win32 shutdown detection setup")

#catches all close signals and passes it to our own functions; all other signals are passed to the original function
def my_wnd_proc(self, hwnd, msg, w_param, l_param):
# Display what we've got.
logging.debug(self.msgdict.get(msg), msg, w_param, l_param)

# Restore the old WndProc. Notice the use of wxin32api
# instead of win32gui here. This is to avoid an error due to
# not passing a callable object.
if msg == win32con.WM_DESTROY:
win32api.SetWindowLong(self.handle,
win32con.GWL_WNDPROC,
self.oldWndProc)

#simplify function for calling
def call_window_proc_old():
return win32gui.CallWindowProc(self.oldWndProc, hwnd, msg, w_param, l_param)

#either call our handle functions or call the original wndProc
return self.message_map.get(msg, call_window_proc_old)()

def hdl_query_end_session(self):
logging.info("WM_QUERYENDSESSION received")
self.shutdown_requested = True
#we have to return 0 here to prevent the windows shutdown until our application is closed
return 0

def hdl_end_session(self):
logging.info("WM_ENDSESSION received")
self.exit_gracefully()
return 0

def hdl_quit(self):
logging.info("WM_QUIT received")
self.shutdown_requested = True
return 0

def hdl_destroy(self):
logging.info("WM_DESTROY received")
return 0

def hdl_close(self):
logging.info("WM_CLOSE received")
self.shutdown_requested = True
return 0

def exit_gracefully(self):
logging.info("shutdown request received")
self.shutdown_requested = True
for func in self._shutdown_functions:
try:
func()
except:
logging.exception("Exception during shutdown function:")
logging.info("shutdown request done, bye!")
exit(0)

def add_cleanup_function(self, function):
self._shutdown_functions.append(function)

And here is some "main" code to start both classes and test it:

if __name__ == "__main__":
import time
from logging.handlers import RotatingFileHandler

#setup own console window
console = SimpleConsole("Test Shutdown")

#setup 3 loggers:
#log debug and info to stdout
#log warning and above to stderr
#log info and above to a file
logging.getLogger().setLevel(logging.DEBUG)
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
logging_path = 'graceful_shutdown_test.log'

rot_file_handler = RotatingFileHandler(logging_path, maxBytes=50 * 1024 * 1024, backupCount=5)
rot_file_handler.setFormatter(formatter)
rot_file_handler.setLevel(logging.INFO)
logging.getLogger().addHandler(rot_file_handler)

log_to_stdout = logging.StreamHandler(sys.stdout)
log_to_stdout.setLevel(logging.INFO)
log_to_stdout.addFilter(lambda record: record.levelno <= logging.INFO)
log_to_stdout.setFormatter(formatter)
logging.getLogger().addHandler(log_to_stdout)

log_to_stderr = logging.StreamHandler()
log_to_stderr.setLevel(logging.WARNING)
log_to_stderr.setFormatter(formatter)
logging.getLogger().addHandler(log_to_stderr)

logging.info("start shutdown test")

#init graceful shutdown with tkinter window handle
shutdown = GracefulShutdown(console.get_window_handle())

counter = 0
counterError = 0

#test cleanup function which runs if shutdown is requested
def graceful_shutdown():
logging.info("start shutdown")
time.sleep(15)
logging.info("stop shutdown")
shutdown.add_cleanup_function(graceful_shutdown)

#main test loop
while not shutdown.shutdown_requested:
console.update()
counter += 1
if counter > 50:
logging.info("still alive")
counter = 0

counterError += 1
if counterError > 150:
logging.error("error for test")
try:
raise NameError("i'm a exception")
except:
logging.exception("exception found!")
counterError = 0
time.sleep(0.1)
shutdown.exit_gracefully()


Related Topics



Leave a reply



Submit