As part of the Client API needed for IPS GUI we need to have the ability
to cancel long running tasks, including long running network tasks. This
needs to handle the scenario where the network goes down during a download:
http://defect.opensolaris.org/bz/show_bug.cgi?id=2201
The current behavior is that the client will hang and needs to be killed.
Danek has proposed a way to allow us to cancel such long running tasks
by injecting a "STOP" string in to the pipe that is reading the download
data, when the user hits cancel. Michal has created a sample PyGTK app
(modified Danek's sample) to do this and it works well. We have put in
place the objects we'd plan to use at a conceptual level in the API to
support this. We use a shared Cancel object to signal from the GUI that
the task is to be stopped and in the case of a Long Running Network task
a separate Watcher Thread to inject the "STOP" string into the pipe to
get the Long Running Network thread to cancel, mid download.
The question we have is how would one override the tar read method to
allow us to do this within IPS?
It also looks like this would the appropriate place to call Progress
Tracker with file chunk size progress info.
JR & Michal
#!/usr/bin/python
from threading import Thread
import time
import select
import os
import sys
try:
import pygtk
pygtk.require("2.0")
except:
pass
try:
import gobject
gobject.threads_init()
import gtk
except:
sys.exit(1)
class PyGtkSpecific:
'''Example pygtk small application, which will allow to stop two types
of long running tasks:
1. Network based long running task
2. Long running task without network activity'''
def __init__(self):
window = gtk.Window(gtk.WINDOW_TOPLEVEL)
box = gtk.HBox(False, 0)
window.add(box)
self.text_box = gtk.Entry(50)
self.text_box.set_editable(False)
self.text_box1 = gtk.Entry(30)
self.text_box1.set_editable(False)
button1 = gtk.Button("Stop long no network")
button2 = gtk.Button("Stop network task")
box.pack_start(self.text_box, True, True, 0)
box.pack_start(button1, True, True, 0)
box.pack_start(button2, True, True, 0)
box.pack_start(self.text_box1, True, True, 0)
button1.show()
button2.show()
self.text_box.show()
self.text_box1.show()
#Two cancel objects, works exactly the same but underlying functions
#are using them differently
self.cancel_no_network=SharedCancelObject()
self.cancel_network=SharedCancelObject()
#Those are two long running tasks, first one is without network
#stuff and the second is showing example usage when we are using
#something from the urrlib2 etc... something which may block us.
Thread(target=self.no_network_task, name="No network", args=[]).start()
Thread(target=self.network_task, name="Network", args=[]).start()
button1.connect_object("clicked", self.do_cancel, self.cancel_no_network)
button2.connect_object("clicked", self.do_cancel, self.cancel_network)
window.connect("destroy", self.do_quit)
box.show()
window.show()
def no_network_task(self):
long_running = LongRunningTask(self, self.cancel_no_network)
try:
long_running.do_something_long()
except CancelException, e:
gobject.idle_add(self.do_print, self.text_box1,("%s") % e)
def network_task(self):
long_running_net = LongRunningNetworkTask(self, self.cancel_network)
try:
long_running_net.download_files()
except CancelException, e:
gobject.idle_add(self.do_print, self.text_box1, ("%s") % e)
def do_cancel(self, shared_cancel_object):
shared_cancel_object.do_cancel()
def do_quit(self, *args):
if not self.cancel_no_network.is_canceled():
print "The long task is still running... canceling"
self.cancel_no_network.do_cancel()
if not self.cancel_network.is_canceled():
print "The network task is still running... canceling"
# Comment out the following self.cancel... line to simulate the hang in bug 2201
# and uncomment pass
self.cancel_network.do_cancel()
# pass
print "Quit main application"
gtk.main_quit()
def do_print(self, text_box, message=None):
text_box.delete_text(0, -1)
text_box.insert_text(message, 0)
def print_progress(self, message):
gobject.idle_add(self.do_print, self.text_box, message)
class SharedCancelObject:
'''This class gives object/objects which may be shared across different
threads to allow cancel functionallity'''
def __init__(self):
self.cancelled = False
def is_canceled(self):
return self.cancelled
def do_cancel(self):
self.cancelled = True
class LongRunningTask:
def __init__(self, progtrack, shared_cancel_object=None):
self.progtrack = progtrack
self.shared_cancel_object = shared_cancel_object
def do_something_long(self):
i = 0
while True:
#condition and will check if the cancel object exists if not
#then it will NOT check if the is_canceled is True and will
#never hit the CancelException
i += 1
if self.shared_cancel_object and self.shared_cancel_object.is_canceled():
raise CancelException, "Long task canceled"
self.progtrack.print_progress("%d LONG TASK" % i)
time.sleep(2)
class LongRunningNetworkTask:
def __init__(self, progtrack, shared_cancel_object=None):
self.progtrack = progtrack
self.pipe_r, self.pipe_w = os.pipe()
self.shared_cancel_object = shared_cancel_object
if self.shared_cancel_object:
Thread(target=self.do_watch, name="Watcher", args=[]).start()
def download_files(self):
i = 0
time.sleep(1)
while True:
i += 1
inl, outl, errl = select.select([sys.stdin, self.pipe_r], [], [],2)
if self.pipe_r in inl:
pipe_s = os.read(self.pipe_r, 100)
if pipe_s == "some uniqe stop uuid!":
raise CancelException, "Network task canceled"
self.progtrack.print_progress("%d NET TASK " % i)
def do_watch(self):
while True:
if self.shared_cancel_object and self.shared_cancel_object.is_canceled():
os.write(self.pipe_w, "some uniqe stop uuid!")
return
time.sleep(0.5)
class CancelException(Exception):
def __init__(self, args=None):
Exception.__init__(self)
self.args = args
if __name__ == '__main__':
PyGtkSpecific()
gtk.main()
_______________________________________________
pkg-discuss mailing list
[email protected]
http://mail.opensolaris.org/mailman/listinfo/pkg-discuss