source: tol/tolsh/tolsh.vfs/lib/tolcomm/tolclient.tcl

devel
Last change on this file was 6d7779d, checked in by Jorge Pérez <josp.jorge@…>, 18 years ago

initial import

git-svn-id: ​https://www.tol-project.org/svn/tolp/trunk@1 be4846ae-f051-483a-831f-b64e7d08527f

  • Property mode set to 100644
File size: 18.5 KB
Line 
1###############################################################################
2#
3#   tolclient.tcl -- Remote client side.
4#
5#   Implement the client communication routines with tolcomm via
6#   sockets.
7#
8###############################################################################
9
10package provide tolclient 1.0
11
12package require tlogger
13
14###############################################################################
15#
16#   tolclient - encapsulate the client code and variable in a
17#   namespace
18#
19###############################################################################
20namespace eval ::tolclient {
21
22  variable data
23
24  array set data {
25    timeout 10
26    error_reason ""
27    mydir ""
28    task,global ""
29    counter,ask 0
30    counter,batch 0
31    cluster,queue ""
32    cluster,launching 0
33  }
34
35  # data(timeout) := number of seconds to wait before canceling a
36  # server's request request, by default it is set to 3 seconds. If
37  # the maximun time is reached without answer from the server, the
38  # server channel is closed and the request results in an error with
39  # a timeout reason
40  #
41
42  #
43  # data(timeout,id,$idrq) := timeout to be used when waiting response
44  # from the server
45  #
46
47  #
48  # data(result,$idrq) := variable to store the result read from the
49  # server for ask $idrq
50  #
51
52  #
53  # data(error_reason) := reason of the last error
54  #
55 
56  #
57  # Directory where I'm located. Will be used later to evaluate the
58  # client part written in TOL
59  #
60  set data(mydir) [file normalize [file dir [info script]]]
61
62  #
63  # data(task,global) := # global list of pending tasks
64  #
65
66  #
67  # data(task,chan,$chan) := list of pending tasks on '$chan'
68  #
69
70  #
71  # data(conn,$chan) := map a connected channel to a server with
72  # its IP,port this information is used to reconnect to the server in
73  # case the connection is lost
74  #
75
76  #
77  # data(conn,$host,$port) := map a host,port to an opened
78  # channel. It's the inverse mapping of data(conn,$chan)
79  #
80}
81
82proc ::tolclient::init { {logfile ""} } {
83  ::tlogger::init tolclient $logfile
84  ::tlogger::level tolclient "error"
85}
86
87proc ::tolclient::log { level msg } {
88 
89  ::tlogger::log tolclient $level $msg
90}
91
92###############################################################################
93#
94#   get_error_reason --
95#
96#
97###############################################################################
98proc ::tolclient::get_error_reason { } {
99
100  variable data 
101  set data(error_reason)
102}
103
104###############################################################################
105#
106#   set_server_timeout -- change the current server's request timeout
107#
108#   Arguments --
109#
110#     timeout - maximun number of seconds to wait a request result from
111#     the server.
112#
113#   Return --
114#
115#     the previous timeout value
116#
117###############################################################################
118proc ::tolclient::set_server_timeout { timeout } {
119  variable data
120
121  old_timeout $data(timeout)
122  set data(timeout) $timeout
123  set old_timeout
124}
125
126###############################################################################
127#
128#   read_channel -- event to read result from server channel
129#
130#     This procedure is invoked whenever an input is available on the
131#     server channel. Every routine must use this event handler in
132#     order to
133#
134#   Arguments --
135#
136#     chan - channel from which we are waiting a line response
137#
138#   Return --
139#
140#     TAG size data_for_tag
141#
142###############################################################################
143proc ::tolclient::read_channel { chan } {
144  variable data
145
146  log "debug" "triggered read_channel $chan"
147
148  if {![eof $chan]} {
149    if {[gets $chan buffer] == -1} {
150      # only handle complete lines
151      log "debug" "read_channel only handle complete lines"
152    } else {
153      process_data $chan $buffer
154    }
155  } else {
156    on_eof_channel $chan
157  }
158  log "debug" "leave : read_channel $chan"
159}
160
161proc ::tolclient::reconnect { host port tasks } {
162
163  variable data
164
165  log "debug" "reconnect $host $port $tasks"
166
167  foreach idtask $tasks {
168    if {[is_waiting $idtask] } {
169      ask_server $host $port "RECONN" "$idtask"
170    }
171  }
172
173  log "debug" "leave reconnect"
174}
175
176#############################################################################
177# process_data --
178#
179#  Process a data 'buffer' received from channel 'chan'. Normally used
180#  to process the end of a time consuming task previously requested to
181#  a 'tol server'. It could process a synchronous event or an
182#  asynchornous one.
183#
184#############################################################################
185proc  ::tolclient::process_data { chan buffer } {
186
187  variable data
188
189  log "debug" "process_data $chan $buffer"
190
191  foreach {host port} $data(conn,$chan) break
192  if {![regexp {([^\s]+)\s(\d+)\s(.+)$} $buffer ==> \
193            cmd size str]} {
194    log "emergency" \
195        "process_data : from server ${host}:${port} : invalid answer format in '$buffer'"
196    return
197  }
198  set rsize [string length $str]
199  if {$size != $rsize} {
200    log "emergency" \
201        "process_data : from server ${host}:${port} : invalid data length : it was $size and should be $rsize"
202    return
203  }
204  switch -- [string toupper $cmd] {
205    CRITICAL {
206      log critical "ERROR from server : $str"
207    }
208    ACKPING {
209      # answer from a PING
210      on_ackping $chan $str
211    }
212
213    RECONN {
214      # asnwer from a reconnection
215      log "debug" "process_data : RECONN $chan $str"
216      if {[regexp {([^\s]+)\s(.+)$} $str ==> idrq idtask]} {
217        on_reconnect $chan $idrq $idtask
218      } else {
219        log "emergency" \
220            "process_data : from server ${host}:${port} : invalid RECONN arguments '$str'"
221      }
222    }
223   
224    TSTART {
225      log "debug" "process_data : TSTART $chan $str"
226      if {[regexp {([^\s]+)\s(.+)$} $str ==> idrq idtask]} {
227        on_start_task $chan $idrq $idtask
228      } else {
229        log "emergency" \
230            "process_data : from server ${host}:${port} : invalid TSTART arguments '$str'"
231      }
232    }
233
234    TFINISH {
235      log "debug" "process_data : TFINISH $chan $str"
236      on_finish_task $chan $str
237    }
238  }
239  log "debug" "leave process_data"
240}
241
242proc ::tolclient::get_conn_channel { host port } {
243 
244  variable data
245
246  set data(conn,$host,$port)
247}
248
249proc ::tolclient::is_waiting { idrq } {
250
251  variable data
252
253  expr {[lsearch $data(task,global) $idrq]!=-1}
254}
255
256proc ::tolclient::get_tasks { chan } {
257
258  variable data
259
260  if {[info exists data(task,chan,$chan)]} {
261    set data(task,chan,$chan)
262  } else {
263    list
264  }
265}
266
267proc ::tolclient::has_pending { chan } {
268
269  variable data
270
271  if {[info exists data(task,chan,$chan)]} {
272    llength $data(task,chan,$chan)
273  } else {
274    return 0
275  }
276}
277
278proc ::tolclient::get_pending { chan } {
279
280  variable data
281
282  if {[info exists data(task,chan,$chan)]} {
283    set data(task,chan,$chan)
284  } else {
285    list
286  }
287}
288
289proc ::tolclient::on_eof_channel { chan } {
290
291  variable data
292
293  log "notice" "enter on_eof_channel $chan"
294
295  # clear the channel state
296  #
297  if {[file channels $chan] ne ""} {
298    fileevent $chan readable {}
299    catch { close $chan }
300  }
301
302  cancel_asking $chan "EOC"
303
304  launch_pending $chan
305
306  clear_channel $chan
307
308  log "notice" "leave on_eof_channel $chan"
309}
310
311proc ::tolclient::on_ackping { chan idrq } {
312
313  variable data
314
315  cancel_timeout $idrq
316
317  # Cancel vwait
318  #
319  set data(vwait,ask,$idrq) ACKPING
320}
321
322#
323# - insert a reconnected task into the list local to the channel
324#
325# - this proc is triggered by the event RECONN received on $chan
326#
327# - the RECONN event is in response to an ask_server generated by
328#   this client, so a vwait variable should be canceled.
329#
330# revisar  todo lo relacionado con el reconnect.
331#
332proc ::tolclient::on_reconnect { chan idrq idtask } {
333 
334  variable data
335
336  log "debug" "enter append_reconnect $chan $idrq $idtask"
337
338  after cancel $data(timeout,id,$idrq)
339
340  # if the task is still waiting
341  if {[is_waiting $idtask]} {
342    # add to the local channel list
343    lappend data(task,chan,$chan) $idtask
344   
345    log "debug" "leave append_reconnect $chan $idrq $idtask"
346   
347    # Cancel vwait
348    #
349    # por aqui no esta pasando ya que esta sentencia tiene un error
350    #
351    set data(vwait,ask,$idrq) $result   
352  } else {
353    set data(vwait,ask,$idrq) ""
354  }
355}
356
357proc ::tolclient::cancel_timeout { idrq } {
358
359  variable data
360
361  after cancel $data(timeout,id,$idrq)
362  array unset data "timeout,id,$idrq"
363}
364
365#
366#
367#
368proc ::tolclient::on_start_task { chan idrq idtask } {
369 
370  variable data
371
372  log "debug" "enter : on_start_task $chan $idrq $idtask"
373
374  cancel_timeout $idrq
375
376  # add the task to the global list of active tasks
377  lappend data(task,global) $idtask
378 
379  # add the task to the local (to this channel) list of active tasks
380  lappend data(task,chan,$chan) $idtask
381
382  cluster_host_busy $chan
383
384  log "debug" "leave : on_start_task $chan $idrq $idtask"
385
386  # Cancel vwait
387  #
388  set data(vwait,ask,$idrq) $idtask
389}
390
391proc ::tolclient::cluster_host_busy { chan } {
392
393  variable data
394
395  foreach {host port} $data(conn,$chan) break
396  set data(cluster,busy,$host,$port) 1
397}
398
399proc ::tolclient::cluster_host_finish { chan } {
400
401  variable data
402
403  foreach {host port} $data(conn,$chan) break
404
405  puts "cluster_host_finish $host $port"
406
407  set data(cluster,busy,$host,$port) 0
408  if {[llength $data(cluster,queue)]} {
409      after idle ::tolclient::cluster_launch
410  }
411}
412
413proc ::tolclient::cluster_try_eval { tolExpr } {
414 
415  variable data
416
417  foreach host_info $data(cluster,hosts) {
418    foreach {host port} $host_info break
419    if {!$data(cluster,busy,$host,$port)} {
420      if {[cluster_try_eval1 $tolExpr $host $port]} {
421        return 1
422      }
423    }
424  }
425  return 0
426}
427
428proc ::tolclient::cluster_try_eval1 { tolExpr host port } {
429 
430  variable data
431
432  puts "cluster_try_eval1 $tolExpr $host $port"
433  set task_id [ask_server $host $port "TOLEVAL" $tolExpr]
434
435  puts "==> $task_id"
436
437  expr {$task_id ne "TIMEOUT"}
438}
439
440#
441# on_finish_task is triggered in response to the asyncronous event TFINISH
442#
443# - should cancel data(vwait,task) in case Wait is running
444#
445proc ::tolclient::on_finish_task { chan idtask } {
446
447  variable data
448
449  log "debug" "enter remove_task $chan $idtask"
450
451  set idx [lsearch $data(task,chan,$chan) $idtask]
452  if {$idx == -1} {
453    log "emergency" "remove_task : task '$idtask' was not pending on channel $chan"
454    set data(vwait,task) ""
455  }
456  set data(task,chan,$chan) [lreplace $data(task,chan,$chan) $idx $idx]
457  set idx [lsearch $data(task,global) $idtask]
458  if {$idx != -1} {
459    set data(task,global) [lreplace $data(task,global) $idx $idx]
460  }
461
462  cluster_host_finish $chan
463
464  log "debug" "leave remove_pending $chan $idtask"
465
466  set data(vwait,task) $idtask
467}
468
469###############################################################################
470#
471#   on_timeout --
472#
473#   Process a timeout event generated while waiting for a an answer
474#   from the server. See ask_server.
475#
476###############################################################################
477proc ::tolclient::on_timeout { chan idrq } {
478 
479  variable data
480
481  log debug "tiggered on_timeout $chan while waiting for $idrq"
482
483  set result [catch {
484    puts $chan "TCANCEL [string length $idrq] VOID $idrq"
485    flush $chan
486  } msgerr]
487  if {$result} {
488    log critical "on_timeout : $msgerr"
489  }
490  # Set result from server to "TIMEOUT" to return from vwait
491  #
492  array unset data "timeout,id,$idrq"
493  set data(vwait,ask,$idrq) "TIMEOUT"
494}
495
496proc ::tolclient::clear_channel { chan } {
497
498  variable data
499
500  foreach {host port} $data(conn,$chan) break
501 
502  array unset data conn,$host,$port
503  array unset data conn,$chan
504  array unset data task,chan,$chan
505  array unset data asking,$chan
506}
507
508proc ::tolclient::launch_pending { chan } {
509
510  variable data
511
512  foreach {host port} $data(conn,$chan) break
513  set pending [get_pending $chan]
514  if {[llength $pending]} {
515    after idle [namespace code [list reconnect $host $port $pending]]
516  }
517}
518
519proc ::tolclient::cancel_asking { chan result } {
520
521  variable data
522
523  if {[llength $data(asking,$chan)]} {
524    set idreqs $data(asking,$chan)
525    log "error" "on_eof_channel while waiting for answer for requests '$idreqs' from server"
526    # Cancel vwait and timeouts
527    #
528    foreach idrq $idreqs {
529      cancel_timeout $idrq
530      set data(vwait,ask,$idrq) $result
531    }
532  }
533}
534
535proc ::tolclient::open_socket {host port} {
536  variable data
537
538  log "debug" "enter open_socket $host $port"
539
540  set chan ""
541  if {[info exists data(conn,$host,$port)]} {
542    set chan $data(conn,$host,$port)
543    if {[file channels $chan] ne ""} {
544      log "debug" "open_socket : reusing channel $chan"
545      set data(conn,$chan) [list $host $port]
546    } else {
547      log "debug" "open_socket : could not reuse channel $chan"
548      set chan ""
549    }
550  }
551  if {$chan eq ""} {
552    log "debug" "open_socket : openning new channel"
553    if {[catch {socket $host $port} chan]} {
554      log "error" $chan
555      set chan ""
556    } else {
557      set data(conn,$host,$port) $chan
558      set data(conn,$chan) [list $host $port]
559      set data(asking,$chan) [list]
560     
561      # buffer processing is line-oriented
562      #
563      fconfigure $chan -blocking 0 -buffering line
564     
565      log "debug" [fconfigure $chan]
566     
567      # Set up a fileevent to handle input from server
568      #
569      fileevent $chan readable [namespace code "read_channel $chan"]
570    }
571  }
572  log "debug" "leave : open_socket : '$chan'"
573  return $chan
574}
575
576# no se usa
577proc ::tolclient::close_conn {host port} {
578  variable data
579
580  if {[info exists $data(conn,$host,$port)]} {
581    if {[file channels $data(conn,$host,$port)] ne ""} {
582      set chan data(conn,$host,$port)
583      if {[has_pending $chan]} {
584        return 0
585      } else {
586        close $chan
587      }
588    }
589  } else {
590    set chan ""
591  }
592  array unset data conn,$host,$port
593  array unset data conn,$chan
594  array unset data asking,$chan
595  return 1
596}
597
598proc ::tolclient::new_id_ask {host port chan} {
599  variable data
600 
601  incr data(counter,ask)
602  set idrq "${host}:${port}:$data(counter,ask)"
603  lappend data(asking,$chan) $idrq
604  return $idrq
605}
606
607###############################################################################
608#
609#   ask_server --
610#
611#   Send a request to the server 'host' which is listening at
612#   'port'. It waits for and inmediate answer.
613#
614###############################################################################
615proc ::tolclient::ask_server { host port TAG {str ""} } {
616 
617  variable data
618
619  log "debug" "enter ask_server $host $port $TAG $str"
620
621  # open channel to server
622  #
623  set chan [open_socket $host $port]
624
625  if {$chan eq ""} {
626    log "debug" "could not open socket ${host}:${port}"
627    return ""
628  }
629  # Send the request to the server
630  #
631  set idrq [new_id_ask $host $port $chan]
632
633  set request [string trimright "$TAG [string length $str] $idrq [string trim $str]"]
634  puts $chan $request
635
636  # Force buffer to be sent to server
637  #
638  flush $chan
639
640  # Set up timeout handler
641  #
642  prepare_timeout $chan $idrq
643 
644  # Wait for result or timeout
645  #
646  vwait ::tolclient::data(vwait,ask,$idrq)
647
648  set result $data(vwait,ask,$idrq)
649  array unset data vwait,ask,$idrq
650  set idx [lsearch $data(asking,$chan) $idrq]
651  set data(asking,$chan) [lreplace $data(asking,$chan) $idx $idx]
652
653  log "debug" "leave ask_server $result"
654
655  set result
656}
657
658proc ::tolclient::prepare_timeout { chan idrq } {
659  variable data
660
661  set data(timeout,id,$idrq) [after [expr {1000*$data(timeout)}] \
662                                  [namespace code "on_timeout $chan $idrq"]]
663}
664
665###############################################################################
666#
667#   server_ping -- Check if a rmtps_server is listening on a remote
668#   host
669#
670#   Arguments --
671#
672#     host - computer where RmtPS_Server is running
673#     port - port where the server socket should be listening
674#
675#   Return --
676#
677#     1 if rmtps_server is running and listening
678#     0 if rmtps_server does not answer or give back the wrong answer
679#
680###############################################################################
681proc ::tolclient::server_ping { host port } {
682
683  log "debug" "enter server_ping $host $port"
684
685  # send request to server
686  #
687  set ans [ask_server $host $port "PING"]
688  set result [regexp "ACKPING" $ans]
689 
690  log "debug" "leave server_ping : $result"
691
692  set result
693}
694
695proc ::tolclient::server_eval { host port tolexpr } {
696 
697  variable data
698
699  log "debug" "enter, server_eval $host $port $tolexpr"
700
701  set answer [ask_server $host $port "TOLEVAL" $tolexpr]
702
703  log "debug" "leave, server_eval : $answer"
704
705  set answer
706}
707
708proc ::tolclient::cluster_eval { args } {
709  log "debug" "enter, cluster_eval [llength $args]"
710
711  set id_batch [cluster_schedule_tasks $args]
712
713  log "debug" "leave, cluster_eval : $id_batch"
714
715  set id_batch
716}
717
718proc ::tolclient::cluster_schedule_tasks { list_expr } {
719 
720  variable data
721
722  incr data(counter,batch)
723  eval lappend data(cluster,queue) $list_expr
724  cluster_launch
725  return "cbatch$data(counter,batch)"
726}
727
728proc ::tolclient::cluster_launch { } {
729
730  variable data
731
732  if {$data(cluster,launching)} return
733  set data(cluster,launching) 1
734  set idx_expr 0
735  foreach tolExpr $data(cluster,queue) {
736    if {![cluster_try_eval $tolExpr]} {
737      break
738    }
739    incr idx_expr
740  }
741  if {$idx_expr} {
742    set data(cluster,queue) [lrange $data(cluster,queue) $idx_expr end]
743  }
744  if {[llength $data(cluster,queue)]} {
745    after idle ::tolclient::cluster_launch
746  }
747  set data(cluster,launching) 0
748}
749
750proc ::tolclient::cluster_new_host { host port } {
751
752  variable data
753
754  if {![info exists data(cluster,busy,$host,$port)]} {
755    lappend data(cluster,hosts) [list $host $port]
756    set data(cluster,busy,$host,$port) 0
757  }
758  puts $data(cluster,hosts)
759}
760
761proc ::tolclient::server_kill { host port } {
762}
763
764proc ::tolclient::server_wait { args } {
765
766  variable data
767
768  log "debug" "enter, server_wait $args"
769
770  if {![llength $args]} {
771    set waiting $data(task,global)
772  } else {
773    set waiting [list]
774    foreach rq $args {
775      if {[lsearch $data(task,global) $rq] != -1} {
776        lappend waiting $rq
777      }
778    }
779  }
780
781  log "debug" "waiting for requests '$waiting'"
782
783  while {[llength $waiting]} {
784    vwait ::tolclient::data(vwait,task)
785    set idx [lsearch $waiting $data(vwait,task)]
786    if {$idx != -1} {
787      set waiting [lreplace $waiting $idx $idx]
788    }
789  }
790
791  log "debug" "leave, server_wait"
792}
793
794###############################################################################
795#
796#  define_TOLAPI --
797#
798#  This proc will evaluate in the protected stack the TOL code
799#  corresponding to the client part of tolclient.
800#
801###############################################################################
802proc ::tolclient::define_TOLAPI { } {
803  variable data
804
805  # check before if tolclient.tol has being loaded
806  #
807  if {[catch {::tol::info variable Code TolClientInitTcl}]} {
808    set fd [open [file join $data(mydir) "tolclient.tol"]]
809    set tol_code [read $fd]
810    close $fd
811    ::tol::console eval -hidden $tol_code
812  }
813}
Note: See TracBrowser for help on using the repository browser.