User:Ian Clark/Immex

From J Wiki
Jump to: navigation, search

Running a background process in J

This is a cut-down demo version of a background process I have running in an experimental utility to watch user activity and furnish support for various coding tasks.

Since J does not multi-thread, the demo takes the form of a duty-cycle which periodically calls the verb duty by means of systimer.

It doesn't matter what duty does. In this demo it happens to increment the noun CYCLE and maintain the time-of-day in another noun called DATETIME, showing these two nouns in a gui window fm, which also has a third unused field for trial gui input.

Paste the code below into a new IJS window and run it. The following user-defined window will appear:


The duty cycle will start-up on loading. Stop it by clicking "STOP".

NB. Duty-cycle experiment. [[User:Ian Clark|Ian Clark]]
NB. Thu 28 Apr 2011 21:07:25

startonload=: fm_run

NB. choose from...
NB. immx=: runimmx1_jijs_
immx=: runimmx0_jijs_

NB. choose from...
NB. post=: immx		NB. NEEDS IN VERB: duty: wd 'msgs'
post=: timexec

NB. =========================================================


FM=: 0 : 0
pc fm;
xywh 6 10 124 12;cc ed00 edit;
xywh 6 25 124 12;cc ed01 edit;
xywh 6 40 124 12;cc ed02 edit;
xywh 130 25 40 12;cc stop button; cn "STOP";
xywh 130 40 40 12;cc go button; cn "GO";
pas 6 6;
rem form end;

duty=: 3 : 0
DATETIME=: 6!:0 'YYYY/MM/DD  hh:mm:ss'
NB. wd 'msgs'
NB. delay 1
1 putinfo CYCLE
2 putinfo DATETIME
if. jbroke'' do. return. end.
post 'duty ',": >:y

fm_close=: 3 : 0
NB. adapted for safe calling if fm isn't there
NB. timexec 0
wd :: 0: 'psel fm; pclose'

fm_go_button=: go

fm_run=: 3 : 0
fm_close''	NB. to destroy any existing form: fm
wd FM
NB. initialize form here
post 'duty 0'
wd 'pshow;'

fm_stop_button=: timexec bind 0

go=: 3 : 0
NB. Handler for GO-button on: fm
NB. (Start afresh using: start'')
post 'duty ',":CYCLE

interval=: (3000 12 31 24 60 60 #. ]) - 3000 12 31 24 60 60 #. [

jbroke=: 3 : 0
	NB. =1 iff yellow-J clicked within last 2 seconds
	NB. Unreliable at midnight before 1st of month
jb=. 9!:46 ''		NB. pathname of jbreak file
t=. >1 { ,(1!:0) <jb	NB. file-last-touched timestamp
s=. t interval (6!:0'')	NB. time (secs) since last touched
2>|s			NB. =1 iff within last 2 seconds

putinfo=: 3 : 0
0 putinfo y
ctl=. 'ed0',":x
wd 'psel fm; set ' , ctl , ' *' , ":y

start=: fm_run

timexec_z_=: 3 : 0
10 timexec y
NB. Execute sentence: y after x milliseconds
NB. Gets called like this:
NB.   timexec 'foo 1'		NB. runs y after default interval
NB.   1000 timexec 'foo 1'	NB. runs y after 1 second
NB.   timexec 0			NB. emergency-stop
NB.   timexec ''		NB. ditto
if. (0=#y) +. (0-:y) do.
  sys_timer_z_=: empty
  wd 'timer 0'
  sys_timer_z_=: timexecCALLBACK bind y
  wd 'timer ',":x

timexecCALLBACK_z_=: 3 : 0
NB. do sentence y after stopping systimer
do=. immx	NB. optional override of stdlib: do
0 timexec 0
do y

0 : 0
timexec 0

NB. =========================================================

Things you can try:

  • In the IJX window enter CYCLE to see its value at the instant Enter is pressed.
  • Ditto DATETIME
  • In the user-defined fm window enter some arbitrary text in the unused edit field. Now in IJX, enter in turn:

JWD creates for you three global nouns in the base locale called ed00, ed01 and ed02 and you will see your text appear in ed00. But only after you have pressed Enter, at which instant all three fields are updated and not before. So don't assume that ed01, say, contains the current time as it appears in fm -- it contains a snapshot of the instant at which you last pressed Enter.

DATETIME on the other hand does contain the up-to-date time, but only while the duty-cycle is running.

In general, duty will be supplied with code-hooks for user options, so it is prone to stopping with a J-error. Now if we set systimer to run its callback every 100 milliseconds, say, then duty will fall off its horse, so to speak, and get dragged along by the reins. The session window IJX will fill with repeated error messages, denying it for user input.

You can however inject a line of input by use of Ctrl+R in some IJS window that happens to be open.

Worse still: the yellow J does not stop the duty-cycle, unless some J code happens to be running, which it does for only a tiny fraction of the time. This is not good behaviour for a background utility supposed to be invisible to the novice user.

By the way, we can arrange for yellow-J to stop the duty-cycle. This is the purpose of verb jbroke, which returns 1 (true) if the yellow-J was clicked in the past 2 seconds. The explicit definition of duty has a line (7) which calls jbroke: comment it out and yellow-J will no longer halt the duty-cycle (reliably).

Getting back to the systimer problem, one remedy is this. It is to arrange for systimer to run its callback (calling duty) once-only. Then, just before duty exits, have it set systimer to run duty again after a given interval. This fail-safe mechanism ensures that if anything prevents duty from completing satisfactorily, the chain is broken and the duty-cycle stops.

Now wd offers no "one-shot mode" for systimer, so we do this by making its handler a saddle-verb timexecCALLBACK which turns off the timer before it runs duty by means of:

  sys_timer_z_=: empty
  wd 'timer 0'

This not only stops systimer but disables the callback handler sys_timer (which we find it good to have reside in the z-locale), so systimer can't start again by accident (which has been known to happen when you play with timers). If J provided a one-shot timer, then this code could be greatly simplified.

But everything in the garden isn't rosy...

Although the demo as shown here is proving to be fairly docile in use, running unobtrusively while the user codes away blissfully in the foreground, systimer turns out to be a nuisance in practice. Several things cause J to crash, at least on the Mac:

  • Exitting J
  • Opening a new IJS window (sometimes)
  • Running: wd 'mbopen'

As these glitches arise, one can arrange for the offending process to call timexec 0 on entry.

closeijxXX_jijs_=: closeijx_jijs_ f.
closeijx_jijs_=: 3 : 0
timexec 0
closeijxXX_jijs_ y

newijsXX_jijs_=: newijs_jijs_ f.
newijs_jijs_=: 3 : 0
timexec 0
newijsXX_jijs_ y

But clearly this is no way to go for a product offering.

A possible alternative to systimer

I've recently learned that it's possible to specify a phrase (9!:27) for execution when, and only when, J returns to "immediate" mode, i.e. when the current process has run to completion and J once more expects session input. You set a latch (9!:29) for one-time execution of the so-called "immex phrase". The system word runimmx0_jijs_ employs 9!:27 and 9!:29 to defer execution of phrase y. Its partner runimmx1_jijs_ acts similarly but outputs the text of y to the session.

The demo provides a word immx as an alias of runimmx0_jijs_. Use this in place of timexec as follows:

   post=: immx

and restart the duty-cycle.

The result? The duty-cycle runs but nothing changes in the fm window and the gui locks solid.

You can use the yellow-J to stop the duty-cycle.

J code is running continuously and system events can't get a look-in. To let them be serviced, uncomment the line in duty which reads:

wd 'msgs'

Now the window fm gets re-painted and you see the numbers change.

But fm still won't accept input. You can also type into the session (IJX) but (on the Mac) as soon as you press Enter, J crashes.

Clearly what is missing is the "time-window", in the duty-cycle, during which both system events and J processes are enabled to operate. The phrase wd 'msgs' lets-in system events, but not J processes, apart from the duty-cycle itself.

I'm keen to know if there is a way round this problem. If so, then post=:immx offers a neater solution to running a background duty-cycle than post=:timexec. But for now I must stick to timexec.

-- Ian Clark <<DateTime(2011-04-28T20:13:33Z)>>