#!/bin/sh # This is a shell archive (produced by GNU sharutils 4.2). # To extract the files from this archive, save it to some FILE, remove # everything before the `!/bin/sh' line above, then type `sh FILE'. # # Made on 2000-03-19 15:00 EST by <jcej@chiroptera.tragus.org>. # Source directory was `/home/jcej/projects/ACE_wrappers/docs/tutorials/013'. # # Existing files will *not* be overwritten unless `-c' is specified. # # This shar contains: # length mode name # ------ ---------- ------------------------------------------ # 386 -rw-rw-r-- hdr # 89 -rw-rw-r-- bodies # 2412 -rw-rw-r-- page01.pre # 432 -rw-rw-r-- page02.pre # 1424 -rw-rw-r-- page03.pre # 1048 -rw-rw-r-- page04.pre # 268 -rw-rw-r-- page05.pre # 914 -rw-rw-r-- page06.pre # 1360 -rw-rw-r-- page07.pre # 860 -rw-rw-r-- page08.pre # 204 -rw-rw-r-- page02.pst # 704 -rw-rw-r-- page04.pst # 385 -rw-rw-r-- page06.pst # 369 -rw-rw-r-- page07.pst # save_IFS="${IFS}" IFS="${IFS}:" gettext_dir=FAILED locale_dir=FAILED first_param="$1" for dir in $PATH do if test "$gettext_dir" = FAILED && test -f $dir/gettext \ && ($dir/gettext --version >/dev/null 2>&1) then set `$dir/gettext --version 2>&1` if test "$3" = GNU then gettext_dir=$dir fi fi if test "$locale_dir" = FAILED && test -f $dir/shar \ && ($dir/shar --print-text-domain-dir >/dev/null 2>&1) then locale_dir=`$dir/shar --print-text-domain-dir` fi done IFS="$save_IFS" if test "$locale_dir" = FAILED || test "$gettext_dir" = FAILED then echo=echo else TEXTDOMAINDIR=$locale_dir export TEXTDOMAINDIR TEXTDOMAIN=sharutils export TEXTDOMAIN echo="$gettext_dir/gettext -s" fi touch -am 1231235999 $$.touch >/dev/null 2>&1 if test ! -f 1231235999 && test -f $$.touch; then shar_touch=touch else shar_touch=: echo $echo 'WARNING: not restoring timestamps. Consider getting and' $echo "installing GNU \`touch', distributed in GNU File Utilities..." echo fi rm -f 1231235999 $$.touch # if mkdir _sh32654; then $echo 'x -' 'creating lock directory' else $echo 'failed to create lock directory' exit 1 fi # ============= hdr ============== if test -f 'hdr' && test "$first_param" != -c; then $echo 'x -' SKIPPING 'hdr' '(file already exists)' else $echo 'x -' extracting 'hdr' '(text)' sed 's/^X//' << 'SHAR_EOF' > 'hdr' && <HTML> <HEAD> X <META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=iso-8859-1"> X <META NAME="Author" CONTENT="James CE Johnson"> X <TITLE>ACE Tutorial 013</TITLE> </HEAD> <BODY TEXT="#000000" BGCOLOR="#FFFFFF" LINK="#000FFF" VLINK="#FF0F0F"> X <CENTER><B><FONT SIZE=+2>ACE Tutorial 013</FONT></B></CENTER> X <CENTER><B><FONT SIZE=+2>Multiple thread pools</FONT></B></CENTER> SHAR_EOF $shar_touch -am 03191459100 'hdr' && chmod 0664 'hdr' || $echo 'restore of' 'hdr' 'failed' if ( md5sum --help 2>&1 | grep 'sage: md5sum \[' ) >/dev/null 2>&1 \ && ( md5sum --version 2>&1 | grep -v 'textutils 1.12' ) >/dev/null; then md5sum -c << SHAR_EOF >/dev/null 2>&1 \ || $echo 'hdr:' 'MD5 check failed' abef9831eba4051526151ff2343730d7 hdr SHAR_EOF else shar_count="`LC_ALL= LC_CTYPE= LANG= wc -c < 'hdr'`" test 386 -eq "$shar_count" || $echo 'hdr:' 'original size' '386,' 'current size' "$shar_count!" fi fi # ============= bodies ============== if test -f 'bodies' && test "$first_param" != -c; then $echo 'x -' SKIPPING 'bodies' '(file already exists)' else $echo 'x -' extracting 'bodies' '(text)' sed 's/^X//' << 'SHAR_EOF' > 'bodies' && PAGE=2 message_queue.cpp mld.h mld.cpp block.h block.cpp task.h task.cpp work.h work.cpp SHAR_EOF $shar_touch -am 1114230198 'bodies' && chmod 0664 'bodies' || $echo 'restore of' 'bodies' 'failed' if ( md5sum --help 2>&1 | grep 'sage: md5sum \[' ) >/dev/null 2>&1 \ && ( md5sum --version 2>&1 | grep -v 'textutils 1.12' ) >/dev/null; then md5sum -c << SHAR_EOF >/dev/null 2>&1 \ || $echo 'bodies:' 'MD5 check failed' 826e1e15e593f64228b867cb6143f179 bodies SHAR_EOF else shar_count="`LC_ALL= LC_CTYPE= LANG= wc -c < 'bodies'`" test 89 -eq "$shar_count" || $echo 'bodies:' 'original size' '89,' 'current size' "$shar_count!" fi fi # ============= page01.pre ============== if test -f 'page01.pre' && test "$first_param" != -c; then $echo 'x -' SKIPPING 'page01.pre' '(file already exists)' else $echo 'x -' extracting 'page01.pre' '(text)' sed 's/^X//' << 'SHAR_EOF' > 'page01.pre' && <P> <HR WIDTH="100%"> <P> My intent with this tutorial was to derive from ACE_Data_Block instead of ACE_Message_Block so that we could leverage the reference counting nature of that object. <P> Along the way, I sort of got distracted... What I ended up with is a poor excuse for ACE_Stream that implements a simple state machine. <P> The application is built around a thread pool where the pool's svc() method takes work units from the message queue for processing. As each unit is taken from the queue, the process() method is invoked to do some work. The twist is that after processing the message, we enqueue it into another thread pool to do more work. This continues through a chain of thread pools until the last where the unit's fini() method is called for finishing up any outstanding work. <P> The chain of thread pools is uni-directional using a singly-linked list of Task derivatives. Each pool has the same number of tasks in order to keep things simple. <P> Kirthika's abstract: <UL> In this tutorial, a singly linked list of thread-pools, each of which is a subtask and which acts as the finite state machine node, is used to simulate a finite state machine. <P> A task is created with a number of subtasks. Once the message block is obtained from the queue, it is verified to see whether a task has a subtask. If so, it is forwarded to the subtask. Thus the mesage traverses over the whole list. As a safety measure for destroying the block after it goes through the whole list, an effective and simple Memory Leak Detector has been implemented. It is a counter which increments when the object where it resides is created and decrements on its deletion. <P> Another optimisation from the previous tutorials on Message Queues, is the bundling of the Data block within the Message Block. The Data block provides reference counting, so duplication of data is avoided. It is deleted only when its reference count drops to zero. Now updating this count between threads call for synchronisation and in comes the ACE_Mutex, a lock which takes care that the counting is thread-safe. <P> Although the example isn't a full-fledged Finite State Machine, i.e. it has to be tweaked to be able to jump states on different inputs, it definitely proves to be a great lesson and introduces us to quite a few new ACE classes and the ways they can be mixed and matched to produce the end-system desired. </ul> SHAR_EOF $shar_touch -am 03191459100 'page01.pre' && chmod 0664 'page01.pre' || $echo 'restore of' 'page01.pre' 'failed' if ( md5sum --help 2>&1 | grep 'sage: md5sum \[' ) >/dev/null 2>&1 \ && ( md5sum --version 2>&1 | grep -v 'textutils 1.12' ) >/dev/null; then md5sum -c << SHAR_EOF >/dev/null 2>&1 \ || $echo 'page01.pre:' 'MD5 check failed' a78a6c7a7841ee5d8ce2a87a55cd456f page01.pre SHAR_EOF else shar_count="`LC_ALL= LC_CTYPE= LANG= wc -c < 'page01.pre'`" test 2412 -eq "$shar_count" || $echo 'page01.pre:' 'original size' '2412,' 'current size' "$shar_count!" fi fi # ============= page02.pre ============== if test -f 'page02.pre' && test "$first_param" != -c; then $echo 'x -' SKIPPING 'page02.pre' '(file already exists)' else $echo 'x -' extracting 'page02.pre' '(text)' sed 's/^X//' << 'SHAR_EOF' > 'page02.pre' && X X <P> <HR WIDTH="100%"> <P> We'll go back to our tradition of looking at main() first. The only change here from our "normal" thread pool is the ability to specify the number of subtasks for the pool. (Each subtask is another thread pool in the chain. I suppose I should have named that better...) I've still got the custom Message_Block so that, at this level, we don't even know about custom Data_Blocks. <P> <HR WIDTH="100%"> SHAR_EOF $shar_touch -am 03191459100 'page02.pre' && chmod 0664 'page02.pre' || $echo 'restore of' 'page02.pre' 'failed' if ( md5sum --help 2>&1 | grep 'sage: md5sum \[' ) >/dev/null 2>&1 \ && ( md5sum --version 2>&1 | grep -v 'textutils 1.12' ) >/dev/null; then md5sum -c << SHAR_EOF >/dev/null 2>&1 \ || $echo 'page02.pre:' 'MD5 check failed' 6f4a2e24d7d776b1ec17a07f00f409f8 page02.pre SHAR_EOF else shar_count="`LC_ALL= LC_CTYPE= LANG= wc -c < 'page02.pre'`" test 432 -eq "$shar_count" || $echo 'page02.pre:' 'original size' '432,' 'current size' "$shar_count!" fi fi # ============= page03.pre ============== if test -f 'page03.pre' && test "$first_param" != -c; then $echo 'x -' SKIPPING 'page03.pre' '(file already exists)' else $echo 'x -' extracting 'page03.pre' '(text)' sed 's/^X//' << 'SHAR_EOF' > 'page03.pre' && X X <P> <HR WIDTH="100%"> <P> I did eventually create that ACE_Data_Block derivative that I wanted. My purpose in doing so was to use the reference-counting that is provided by ACE_Data_Block and ACE_Message_Block interactions. X When you're working with an object in a single thread, it's generally not so difficult to manage it's lifetime. That is, it doesn't tend to go out of scope or get destroyed unless you do it on purpose. <P> On the other hand, if you're passing data between several threads, it is easy to loose track of who "owns" the data at any one time. All too frequently, data will be deleted by one thread while another is still using it. Reference counting can prevent that. The rule of thumb is that you increment the reference count of the object when you hand it off to a new thread. You then decrement the count when you're done with the object and let the object delete itself when there are no more references. <P> To prove that all of that works correctly in the tutorial, I've created a cheap Memory Leak Detector object. All mld instances reference a thread-safe counter that is incremented when the mld is constructed and decremented when destructed. I then insert an mld into each of my dynamically created objects. If I get to the end of main() and the counter isn't zero then I either didn't delete enough or I deleted too many times. <P> Simple, cheap, effective. <P> <HR WIDTH="100%"> SHAR_EOF $shar_touch -am 03191459100 'page03.pre' && chmod 0664 'page03.pre' || $echo 'restore of' 'page03.pre' 'failed' if ( md5sum --help 2>&1 | grep 'sage: md5sum \[' ) >/dev/null 2>&1 \ && ( md5sum --version 2>&1 | grep -v 'textutils 1.12' ) >/dev/null; then md5sum -c << SHAR_EOF >/dev/null 2>&1 \ || $echo 'page03.pre:' 'MD5 check failed' 545e09ebf801bfaea60bb1fb07c7ec9f page03.pre SHAR_EOF else shar_count="`LC_ALL= LC_CTYPE= LANG= wc -c < 'page03.pre'`" test 1424 -eq "$shar_count" || $echo 'page03.pre:' 'original size' '1424,' 'current size' "$shar_count!" fi fi # ============= page04.pre ============== if test -f 'page04.pre' && test "$first_param" != -c; then $echo 'x -' SKIPPING 'page04.pre' '(file already exists)' else $echo 'x -' extracting 'page04.pre' '(text)' sed 's/^X//' << 'SHAR_EOF' > 'page04.pre' && X X <P> <HR WIDTH="100%"> <P> Let's look now at the changes to our ACE_Message_Block derivative and the new ACE_Data_Block derivative. <P> The important thing to remember is that the data block (not the message block) is reference counted. When you instantiate a new ACE_Message_Block, it will create one or more ACE_Data_Block objects to contain the data you need. Optionally, you can provide it with a pointer to a data block. <P> When you finish with a message block, you should use the release() method to make it go away. Do not ever <em>delete</em> an instance of a message block! When you invoke release(), the message block will invoke release() on the data block(s) it contains. If the block's reference count goes to zero as a result then the block will <em>delete</em> itself. <P> To increment the reference count of a data block, use the duplicate() method of the message block (or blocks) to get a new message block referencing the same data block. This is very efficient since the actual data is not copied. <P> <HR WIDTH="100%"> SHAR_EOF $shar_touch -am 03191459100 'page04.pre' && chmod 0664 'page04.pre' || $echo 'restore of' 'page04.pre' 'failed' if ( md5sum --help 2>&1 | grep 'sage: md5sum \[' ) >/dev/null 2>&1 \ && ( md5sum --version 2>&1 | grep -v 'textutils 1.12' ) >/dev/null; then md5sum -c << SHAR_EOF >/dev/null 2>&1 \ || $echo 'page04.pre:' 'MD5 check failed' c8b4750a824380f2effc43557c8540ad page04.pre SHAR_EOF else shar_count="`LC_ALL= LC_CTYPE= LANG= wc -c < 'page04.pre'`" test 1048 -eq "$shar_count" || $echo 'page04.pre:' 'original size' '1048,' 'current size' "$shar_count!" fi fi # ============= page05.pre ============== if test -f 'page05.pre' && test "$first_param" != -c; then $echo 'x -' SKIPPING 'page05.pre' '(file already exists)' else $echo 'x -' extracting 'page05.pre' '(text)' sed 's/^X//' << 'SHAR_EOF' > 'page05.pre' && X X <P> <HR WIDTH="100%"> <P> On this page we have the code for the Data_Block and Message_Block objects. As you probably suspect from the header on the previous page, the complicated part is in the construction and destruction of the Data_Block. <P> <HR WIDTH="100%"> SHAR_EOF $shar_touch -am 03191459100 'page05.pre' && chmod 0664 'page05.pre' || $echo 'restore of' 'page05.pre' 'failed' if ( md5sum --help 2>&1 | grep 'sage: md5sum \[' ) >/dev/null 2>&1 \ && ( md5sum --version 2>&1 | grep -v 'textutils 1.12' ) >/dev/null; then md5sum -c << SHAR_EOF >/dev/null 2>&1 \ || $echo 'page05.pre:' 'MD5 check failed' a95fdcd3db2356b091228728f4f3f130 page05.pre SHAR_EOF else shar_count="`LC_ALL= LC_CTYPE= LANG= wc -c < 'page05.pre'`" test 268 -eq "$shar_count" || $echo 'page05.pre:' 'original size' '268,' 'current size' "$shar_count!" fi fi # ============= page06.pre ============== if test -f 'page06.pre' && test "$first_param" != -c; then $echo 'x -' SKIPPING 'page06.pre' '(file already exists)' else $echo 'x -' extracting 'page06.pre' '(text)' sed 's/^X//' << 'SHAR_EOF' > 'page06.pre' && X X <P> <HR WIDTH="100%"> <P> Let's take a look now at the new Task object. This will obviously be different from the Tasks we've created before but I think you'll be surprised at how relatively simple it actually is. <P> Remember that the goal of this tutorial was to use the reference counting abilities of the ACE_Data_Block. The only way to show that effectively is to have a data block passed between different threads. A thread pool isn't really going to do that so, instead, our new Task can be part of a chain of tasks. In that way, each Task can pass the data on to another and satisfy our need for moving the ACE_Data_Block around. If we've done the reference counting correctly then none of our tasks will be trying to work with deleted data and we won't have any memory leaks at the end. <P> There's not much to the header, so I've included it and the cpp file on this one page. <P> <HR WIDTH="100%"> SHAR_EOF $shar_touch -am 03191459100 'page06.pre' && chmod 0664 'page06.pre' || $echo 'restore of' 'page06.pre' 'failed' if ( md5sum --help 2>&1 | grep 'sage: md5sum \[' ) >/dev/null 2>&1 \ && ( md5sum --version 2>&1 | grep -v 'textutils 1.12' ) >/dev/null; then md5sum -c << SHAR_EOF >/dev/null 2>&1 \ || $echo 'page06.pre:' 'MD5 check failed' a4c9b50df3240c5134733d2033fd5f03 page06.pre SHAR_EOF else shar_count="`LC_ALL= LC_CTYPE= LANG= wc -c < 'page06.pre'`" test 914 -eq "$shar_count" || $echo 'page06.pre:' 'original size' '914,' 'current size' "$shar_count!" fi fi # ============= page07.pre ============== if test -f 'page07.pre' && test "$first_param" != -c; then $echo 'x -' SKIPPING 'page07.pre' '(file already exists)' else $echo 'x -' extracting 'page07.pre' '(text)' sed 's/^X//' << 'SHAR_EOF' > 'page07.pre' && X X <P> <HR WIDTH="100%"> <P> I've been trying to justify the chain of tasks by talking about a Work object that implements a state machine. The idea is that your Work object has to perform a series of discrete steps to complete it's function. Traditionally, all of those steps would take place in one thread of execution. That thread would probably be one from a Task thread pool. <P> Suppose, however, that some of those steps spend a lot of time waiting for disk IO. You could find that all of your thread-pool threads are just sitting there waiting for the disk. You might then be tempted to increase the thread pool size to get more work through. However, if some of the stages are memory intensive, you could run out of memory if all of the workers get to that state at the same time. <P> One solution might be to have different thread pools for each state. Each pool could have it's size tuned appropriately for the work that would be done there. That's where the chain of Tasks comes in. X In this tutorial's implementation I've taken the easy route and set all of the thread pools to the same size but a more realistic solution would be to set each thread pool in the chain to a specific size as needed by that state of operation. <P> There's not much to this header either so I've combined it with the cpp file as with task. <P> <HR WIDTH="100%"> SHAR_EOF $shar_touch -am 03191459100 'page07.pre' && chmod 0664 'page07.pre' || $echo 'restore of' 'page07.pre' 'failed' if ( md5sum --help 2>&1 | grep 'sage: md5sum \[' ) >/dev/null 2>&1 \ && ( md5sum --version 2>&1 | grep -v 'textutils 1.12' ) >/dev/null; then md5sum -c << SHAR_EOF >/dev/null 2>&1 \ || $echo 'page07.pre:' 'MD5 check failed' 17c1c426089b288e418c3f62fdc09744 page07.pre SHAR_EOF else shar_count="`LC_ALL= LC_CTYPE= LANG= wc -c < 'page07.pre'`" test 1360 -eq "$shar_count" || $echo 'page07.pre:' 'original size' '1360,' 'current size' "$shar_count!" fi fi # ============= page08.pre ============== if test -f 'page08.pre' && test "$first_param" != -c; then $echo 'x -' SKIPPING 'page08.pre' '(file already exists)' else $echo 'x -' extracting 'page08.pre' '(text)' sed 's/^X//' << 'SHAR_EOF' > 'page08.pre' && X X <P> <HR WIDTH="100%"> <P> And that's the end of another tutorial. This one is probably the most complicated so far because I've introduced or expanded upon a number of different concepts. Namely: state machines, reference counting and task chaining. I hope I didn't complicate things to the point where the lesson got lost in the noise. As always, feel free to drop a note to the ACE-Users mailing list if you feel that some of this could use a little more explaination. X <P> <UL> <LI><A HREF="Makefile">Makefile</A> <LI><A HREF="block.cpp">block.cpp</A> <LI><A HREF="block.h">block.h</A> <LI><A HREF="message_queue.cpp">message_queue.cpp</A> <LI><A HREF="mld.cpp">mld.cpp</A> <LI><A HREF="mld.h">mld.h</A> <LI><A HREF="task.cpp">task.cpp</A> <LI><A HREF="task.h">task.h</A> <LI><A HREF="work.cpp">work.cpp</A> <LI><A HREF="work.h">work.h</A> </UL> <P> SHAR_EOF $shar_touch -am 03191459100 'page08.pre' && chmod 0664 'page08.pre' || $echo 'restore of' 'page08.pre' 'failed' if ( md5sum --help 2>&1 | grep 'sage: md5sum \[' ) >/dev/null 2>&1 \ && ( md5sum --version 2>&1 | grep -v 'textutils 1.12' ) >/dev/null; then md5sum -c << SHAR_EOF >/dev/null 2>&1 \ || $echo 'page08.pre:' 'MD5 check failed' b761e40eff75cbf3174b53b4b0c5c172 page08.pre SHAR_EOF else shar_count="`LC_ALL= LC_CTYPE= LANG= wc -c < 'page08.pre'`" test 860 -eq "$shar_count" || $echo 'page08.pre:' 'original size' '860,' 'current size' "$shar_count!" fi fi # ============= page02.pst ============== if test -f 'page02.pst' && test "$first_param" != -c; then $echo 'x -' SKIPPING 'page02.pst' '(file already exists)' else $echo 'x -' extracting 'page02.pst' '(text)' sed 's/^X//' << 'SHAR_EOF' > 'page02.pst' && <HR WIDTH="100%"> <P> Nothing really surprising here... Just remember that your total number of threads is ( ( 1 + subtasks ) * threads ). You probably don't want to get too carried away with that! <P> SHAR_EOF $shar_touch -am 03191459100 'page02.pst' && chmod 0664 'page02.pst' || $echo 'restore of' 'page02.pst' 'failed' if ( md5sum --help 2>&1 | grep 'sage: md5sum \[' ) >/dev/null 2>&1 \ && ( md5sum --version 2>&1 | grep -v 'textutils 1.12' ) >/dev/null; then md5sum -c << SHAR_EOF >/dev/null 2>&1 \ || $echo 'page02.pst:' 'MD5 check failed' a8c43c5c68518f6eb8c03701d1603a92 page02.pst SHAR_EOF else shar_count="`LC_ALL= LC_CTYPE= LANG= wc -c < 'page02.pst'`" test 204 -eq "$shar_count" || $echo 'page02.pst:' 'original size' '204,' 'current size' "$shar_count!" fi fi # ============= page04.pst ============== if test -f 'page04.pst' && test "$first_param" != -c; then $echo 'x -' SKIPPING 'page04.pst' '(file already exists)' else $echo 'x -' extracting 'page04.pst' '(text)' sed 's/^X//' << 'SHAR_EOF' > 'page04.pst' && <HR WIDTH="100%"> <P> One of the most difficult parts of this to get right was the Lock object. I didn't even have it in the beginning but I soon realized that the reference counts were getting weird. A little careful reading of the comments and the source informed me that some sort of locking is necessary to keep the counter sane. The simplest thing at that point was to use the ACE_Lock_Adaptor<> to adapt ACE_Mutex appropriately. The next trick was to ensure that the lock object was destroyed at the proper time to prevent both memory leaks and core dumps. The finaly product may be a little bit intimidating at first but it's really quite simple once you understand the motivation. <P> SHAR_EOF $shar_touch -am 03191459100 'page04.pst' && chmod 0664 'page04.pst' || $echo 'restore of' 'page04.pst' 'failed' if ( md5sum --help 2>&1 | grep 'sage: md5sum \[' ) >/dev/null 2>&1 \ && ( md5sum --version 2>&1 | grep -v 'textutils 1.12' ) >/dev/null; then md5sum -c << SHAR_EOF >/dev/null 2>&1 \ || $echo 'page04.pst:' 'MD5 check failed' 325565f3f72961b842b612caeb93b36a page04.pst SHAR_EOF else shar_count="`LC_ALL= LC_CTYPE= LANG= wc -c < 'page04.pst'`" test 704 -eq "$shar_count" || $echo 'page04.pst:' 'original size' '704,' 'current size' "$shar_count!" fi fi # ============= page06.pst ============== if test -f 'page06.pst' && test "$first_param" != -c; then $echo 'x -' SKIPPING 'page06.pst' '(file already exists)' else $echo 'x -' extracting 'page06.pst' '(text)' sed 's/^X//' << 'SHAR_EOF' > 'page06.pst' && <HR WIDTH="100%"> <P> So you see... it wasn't really that much more complicated. We really just have to remember to pass to <i>next_</i> when we finish working on the data. If your Unit_Of_Work derivative is going to implement a state machine be sure that you also implement a fini() method <em>or</em> ensure that your chain of subtasks is large enough for all possible states. <P> SHAR_EOF $shar_touch -am 03191459100 'page06.pst' && chmod 0664 'page06.pst' || $echo 'restore of' 'page06.pst' 'failed' if ( md5sum --help 2>&1 | grep 'sage: md5sum \[' ) >/dev/null 2>&1 \ && ( md5sum --version 2>&1 | grep -v 'textutils 1.12' ) >/dev/null; then md5sum -c << SHAR_EOF >/dev/null 2>&1 \ || $echo 'page06.pst:' 'MD5 check failed' 65a1b8bc21034187e0885cc07fa7734f page06.pst SHAR_EOF else shar_count="`LC_ALL= LC_CTYPE= LANG= wc -c < 'page06.pst'`" test 385 -eq "$shar_count" || $echo 'page06.pst:' 'original size' '385,' 'current size' "$shar_count!" fi fi # ============= page07.pst ============== if test -f 'page07.pst' && test "$first_param" != -c; then $echo 'x -' SKIPPING 'page07.pst' '(file already exists)' else $echo 'x -' extracting 'page07.pst' '(text)' sed 's/^X//' << 'SHAR_EOF' > 'page07.pst' && <HR> <P> And that is that. For a more complex machine that may want to "jump states" you would have to set some "state information" (sorry, bad choice of terminology again) so that process() could decide what to do at each call. You might also modify Task::svc() so that it will respect the return value of process() and do something useful with the information. <P> SHAR_EOF $shar_touch -am 03191459100 'page07.pst' && chmod 0664 'page07.pst' || $echo 'restore of' 'page07.pst' 'failed' if ( md5sum --help 2>&1 | grep 'sage: md5sum \[' ) >/dev/null 2>&1 \ && ( md5sum --version 2>&1 | grep -v 'textutils 1.12' ) >/dev/null; then md5sum -c << SHAR_EOF >/dev/null 2>&1 \ || $echo 'page07.pst:' 'MD5 check failed' 21f1bb3615bd4ba5efe5ec25bb895c0e page07.pst SHAR_EOF else shar_count="`LC_ALL= LC_CTYPE= LANG= wc -c < 'page07.pst'`" test 369 -eq "$shar_count" || $echo 'page07.pst:' 'original size' '369,' 'current size' "$shar_count!" fi fi rm -fr _sh32654 exit 0