0

My scenario is: using tcl, I am writing a file. then I am sourcing that file and want to keep a watch on log file that the file will generate during its execution. If the size of log file does not change after 2 hours then I need to stop the execution of file and rerun tcl script which will regenerate the file and then source it(generate-source cycle continue till file execution done completely)

Here is psedo code of my scenario :

set root /home/nikhil/
set group {all}
set TopScript [open $root/TopScript.tcl w] 
    puts $TopScript "[exec perl $root/extract_excel.pl $group] \n}"
    Puts $TopScript "set logfilename $root/logfile"
    puts $TopScript "source $root/main_1.tcl"
    puts $TopScript "source $root/main_2.tcl"
    close $TopScript

#Pseudo code for scenario what I want is:

thread::create {
   exec tclsh /home/nikhil/TopScript.tcl
   thread::wait
}

thread::create {
 set flag_for_interupt 0

  while{!flag_for_interupt} {
       set old_log_size [file size $root/logfile]
       after [expr {int(1000* 60* 60* 2)}]
       set new_log_size [file size $root/logfile]

       if{$old_log_size == $new_log_size} {
           puts "I suspect Test is in hung state.... checking again after 2 hours.....\n"
           after [expr {int(1000* 60* 60* 2)}]
           set $new_log_size [file size $root/logfile]
           if{$old_log_size == $new_log_size} {
               puts "\n\n Test is in hung state.... log has not updated since last 4 hours........\n\n"
           }
           ##########  enter code to interupt main thread and execute again
           set flag_for_inturept 1
        }

    } 
 }
4
  • What is the question? Commented Nov 16, 2017 at 9:08
  • A shot in the dark: you really want to ask «when a log watcher thread detects a "stale log file" condition, how do I communicate this fact to another thread?», do you? Commented Nov 16, 2017 at 9:10
  • Yes ...I am a beginner for TCL and also don't know how to print string from two threads.. .. so just gave a full picture of scenario.. so anybody may suggest another approach Commented Nov 16, 2017 at 11:13
  • OK, then use [thread::send] in one thread and [vwait] in another; here's how. Commented Nov 16, 2017 at 11:16

1 Answer 1

3

Tcl does not share (normal) variables between threads. Instead, you need to work by sending messages between threads. A message is just a (usually short) script that you ask the other thread to run (the result of the script can be handled in a few ways, including a synchronous wait or by running the script disconnected). Most of the time, you set up a procedure in the receiving thread to actually do the work.

Let's restructure your waiting thread to operate that way:

set waiter [thread::create {
    proc do {filename targetThread returnMessage} {
        set delay [expr {int(1000* 60* 60* 2)}]
        while true {
            # This would be really a do-while loop, but we don't have those
            while true {
                set old_log_size [file size $filename]
                after $delay
                set new_log_size [file size $filename]
                if {$old_log_size == $new_log_size} break
            }

            puts "I suspect Test is in hung state... checking again after 2 hours...\n"

            after $delay
            set new_log_size [file size $filename]
            if {$old_log_size == $new_log_size} break
        }

        puts "\n\n Test is in hung state... log has not updated since last 4 hours...\n\n"

        # Send message to main thread to do something about the hung test
        thread::send -async $targetThread $returnMessage
    }
    thread::wait
}]

We'd set that thread actually working like this:

thread::send -async $waiter [list do $root/logfile [thread::current] {set test_hung 1}]

However, the only long operations in there are the calls to after. (Well, unless you're fantastically unlucky with the OS calls to get the log file size.) That means we can convert to using an asynchronous form in the thread, leaving the thread open to being accessed while it is working.

set waiter [thread::create {
    proc do {filename targetThread returnMessage} {
        set delay [expr {int(1000* 60* 60* 2)}]
        set old_log_size [file size $filename]
        # Schedule the run of do2 in two hours
        after $delay [list do2 $filename $targetThread $returnMessage $delay $filename $old_log_size]
    }

    proc do2 {filename targetThread returnMessage delay filename old_log_size} {
        set new_log_size [file size $filename]
        if {$old_log_size == $new_log_size} {
            puts "I suspect Test is in hung state... checking again after 2 hours...\n"

            # Schedule the run of do3 in another two hours
            after $delay [list do3 $filename $targetThread $returnMessage $delay $filename $old_log_size]
        } else {
            # An update did happen; run ourselves again in two hours to compare to the new size
            after $delay [list do2 $filename $targetThread $returnMessage $delay $filename $new_log_size]
        }
    }

    proc do3 {filename targetThread returnMessage delay filename old_log_size} {
        set new_log_size [file size $filename]
        if {$old_log_size == $new_log_size} {
            puts "\n\n Test is in hung state... log has not updated since last 4 hours...\n\n"

            # Send message to main thread to do something about the hung test
            thread::send -async $targetThread $returnMessage
        } else {
            # An update did happen; run ourselves again in two hours to compare to the new size
            after $delay [list do2 $filename $targetThread $returnMessage $delay $filename $new_log_size]
        }
    }
    thread::wait
}]

So… we've got manageability but lost readability (the API for use is identical). Not bad but not great! (This sort of restructuring is known as conversion to Continuation-Passing Form, and it tends to destroy code readablity.) In 8.6 we can do better because we have coroutines that can yield to the thread's event loop.

set waiter [thread::create {
    proc do {filename targetThread returnMessage} {
        coroutine Coro[incr ::Coro] doBody $filename $targetThread $returnMessage
    }
    proc delayForTwoHours {} {
        set delay [expr {int(1000* 60* 60* 2)}]
        after $delay [info coroutine]
        yield
    }

    proc doBody {filename targetThread returnMessage} {
        while true {
            while true {
                set old_log_size [file size $filename]
                delayForTwoHours
                set new_log_size [file size $filename]
                if {$old_log_size == $new_log_size} break
            }

            puts "I suspect Test is in hung state... checking again after 2 hours...\n"

            delayForTwoHours
            set new_log_size [file size $filename]
            if {$old_log_size == $new_log_size} break
        }

        puts "\n\n Test is in hung state... log has not updated since last 4 hours...\n\n"

        # Send message to main thread to do something about the hung test
        thread::send -async $targetThread $returnMessage
    }
    thread::wait
}]

That (which still has the same API calling convention) gives manageability yet keeps virtually all the code (especially apart from the short bits in their own procedures) looking the same as the first version I wrote. Under the covers, the coroutine does the rewriting to continuation-passing form, but that's now handled by the Tcl runtime instead of needing to be done explicitly in your code. (Also, Tcl uses explicit coroutine launching, but that in turn means that it can yield across multiple stack levels without the complex yield chains of some other languages.)


I leave it as an exercise to use the second or third version as the basis for a version of the code which doesn't need extra threads at all. Running processes in the background also doesn't need threads; this whole management process can work with just a single (user-visible) thread.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.