
TOPIC:  NET_NAME(), THE NETNAME() SUBSTITUTE
AUTHOR:  DON POWELLS
DATE:  13 Jan 1988
TEXT:
--------------------------------------------------------------------
NET_NAME(), THE NETNAME() SUBSTITUTE          Published: 13 Jan 1988
Don Powells                      Copyright (c) 1988, Nantucket Corp.
--------------------------------------------------------------------

The NETNAME() function does not return anything in the Summer 87
version of Clipper.  I wrote the function below to replace it as an
interim solution.  It is called NET_NAME() and is invoked in the
same manner as the original function.  The source code for the
assembly function and a Clipper test program which calls it are
included in this document file.

Happy Clipping
Don Powells, Tech Info Spec.

* Net_Name Source Code

;*******************************
;* Filename: NET_NAME.ASM
;* Purpose: To replace the faulty netname() function
;*          in Clipper Summer 87
;* Usage: user = NET_NAME()
;* Notes: To assemble and link this function with your
;*        program you follow these steps:
;*             MASM NET_NAME;
;*             CLIPPER YOUR_PROG
;*             PLINK86 FI YOUR_PROG,NET_NAME LIB CLIPPER

PUBLIC          NET_NAME

EXTRN           __RETC:FAR

DGROUP  GROUP   DATASEG
DATASEG         SEGMENT PUBLIC  '_DATA' ; _DATA IS THE REQUIRED CLASS

MACHINE DB      16 DUP  (?)     ;       CODE FOR SUMMER 87
DATASEG         ENDS

CODESEG         SEGMENT BYTE    'CODE'  ; CODE IS THE REQUIRED CLASS
                ASSUME  CS:CODESEG,DS:DATASEG  ;  CODE FOR SUMMER 87

NET_NAME        PROC    FAR

;* INITIALIZATION PROCEDURE
        PUSH    BP
        MOV     BP,SP

        PUSH    DS
        PUSH    DX
        PUSH    CX

;* GET THE MACHINE NAME
        MOV     AX,5E00H
        MOV     DX,SEG MACHINE  ; DS:DX = ADDRESS OF BUFFER
        MOV     DS,DX
        MOV     DX,OFFSET MACHINE
        INT     21H

;* RETURN THE MACHINE NAME TO CLIPPER
        PUSH    DS
        PUSH    DX
        MOV     DX,SEG DGROUP ; DS MUST BE POINTING TO DGROUP SEGMENT
        MOV     DS,DX
        CALL    __RETC
        ADD     SP,4

;* TERMINATION PROCEDURE
        POP     CX
        POP     DX
        POP     DS
        POP     BP
        RET

NET_NAME        ENDP
CODESEG         ENDS
                END

* Tnet Source Code

*************
* Filename: TNET.PRG
* Purpose: To test the Net_name() Assembly function
*             written to replace Clipper's (Summer 87)
*             non-functioning NetName() function
* Returns: The machine name of the local computer on a
*          Local Area Network
* Notes: This function only works with the IBM PC network
*             program or with Novell 2.0A when the string
*             MACHINENAME="username" appears in the user's
*             login script. 'Username' can be any string of
*             characters.
*************

SET COLOR TO W/B,,B,B
CLEAR
@ 5,5 SAY "TESTING THE NET_NAME() FUNCTION"
USER = NET_NAME()
?
? "The machine name is: "
?? USER
WAIT
RETURN


TOPIC:  NETWORK BIBLIOGRAPHY
AUTHOR:  NANTUCKET SUPPORT
DATE:  14 Aug 1987
TEXT:

For general network information, you may wish to refer to the
following sources:

PERIODICALS
-----------

Data Based Advisor, Vol. 5, No. 6, June 1987.  Database Management
     Systems feature issue.  A dozen short articles on network
     database products.  Some concept oriented articles, mostly
     product oriented articles.  Among dBASE language LAN products,
     most substantial coverage on Quicksilver; passing coverage on
     dBASE III Plus, FoxBASE+ and Clipper.

"Data Managers and LANs," PC Tech Journal, Vol. 5, No. 5, May 1987,
     p. 54.  Good 10-page article on multi-user database concepts.
     Contrasts implementations by various PC LAN database products.
     Also contrast multi-user database approaches on mainframes
     with those on micros, and likely convergence between the two.

"LAN Evaluation Report 1986," Novell, Inc.  75 pages.
"LAN Operating System Report," Novell, Inc.  125 pages.

     Objective presentation of LAN concepts and benchmarks designed
     to educate, and to highlight Novell products.  Not highly
     promotional; educational component is technical and excellent.
     Heavy detail suited to the technical person not yet familiar
     with LAN hardware and software specifically.  Implementation
     algorithms of Novell and competitors' hardware and software
     products.  Explanation of MS-DOS, NETBIOS, other assorted
     buzzwords.  Available from:

         Novell, Inc.
         748 North 1340 West
         Orem, UT  84057
         (801) 226-8202

LAN Times, monthly product newletter by Novell, Inc.  About 50
     pages per issue.  Focuses heavily on Novell's products.  Also
     other products that work with Novell networks.  Some articles
     on concepts with insights for interested but non-expert
     programmers.


"NetWare in Control," PC Tech Journal, November 1985.  Good 10-
     page summary of features and functions of Novell NetWare.
     Comparison with IBM PC Network.

PC Magazine, Vol. 4, No. 3, February 5, 1985.  Cover feature:
    "Networks Challenge Space and Time."  This issue includes eight
     articles on various aspects of networks, including general
     introduction, reviews of specific products, and benchmark
     measurements of their performance.  Over a dozen
     manufacturer's networks reviewed.  A little dated but still
     largely apt.

PC Magazine, Vol. 5, No. 21, December 9, 1986.  Special LAN issue.
    Several articles on LANs.  Focus on characteristics of each of
    over half a dozen network vendors' offerings.

PC Week.  Weekly Connectivity pullout section.  Starting in
    February, 1987, a 30-50 page supplement comes with each issue.

    Current developments and future direction in PC LANs,
    micro-mainframe links, protocols.  Numerous case studies of
    installations at various companies, government agencies, and
    other institutions.  Numerous product advertisements.

PC WORLD Magazine, Feb 1985.  Networks by six manufacturers
    reviewed.


BOOKS
-----

dBASE III Plus and Local Area Networks, W. Bates and A. Fortino,
    Ashton-Tate, 1986.  300 page book.  Content falls in two
    categories.  First half discusses LANs themselves.  Second half
    covers database systems on LANs with emphasis and examples from
    dBASE III Plus.  Examples of how resource contention is handled
    in dBASE III Plus.

Local Area Networks, W. Stallings, Macmillan, 1984.  Textbook on
    Local Area Networks in the broad sense, connecting not only PCs
    but minicomputers, telephone equipment, and other networks with
    one another.

Principles of Database Systems, J. Ullman, Computer Science Press.
    This is a textbook, replete with theorems and proofs.  It
    contains a 40-page chapter dealing with concurrent database
    operations.


 --End: NETWORK BIBLIOGRAPHY
 /FOR


TOPIC:
NETWORK PROGRAMMING WITH CLIPPER
AUTHOR:
DAVID MORGAN
DATE:
14 Aug 1987
TEXT:
--------------------------------------------------------------------
Network Programming With Clipper
David Morgan
--------------------------------------------------------------------
This article first appeared in Nantucket News, Vol 1, No 4.  The
MININET source code referenced in this article is contained in the
NANNWS14.ARC file in the NANSIG File Library.

Clipper Autumn '86 introduced tools that allow your applications to
run on networks. You can gain an understanding of the mechanics  of
each command and function from the manual supplement and the
NETWORK demo, but most of us with single user experience need to
develop new habits to use these commands effectively.

Knowing how to use the commands is one thing; knowing when and
where to do so is another. This article focuses on some common
network application design questions, suggests related programming
techniques, and illustrates them with a mini-application called
MININET.


A Brief Overview

A network database application's mission is to allow controlled
data sharing.  The sharing takes the form of a new way to open
files called shared mode.  It is provided by the network operating
system and utilized by Clipper.  It allows two or more users to
open the same file.  The control takes the form of lock functions.
They temporarily override the shareability of data in favor of
private control by a single user.


Key Issues

Newcomers to Clipper network programming need to familiarize
themselves with the following key issues to write effective network
applications:

o mechanics of locking
o how to open files
o when to seek a lock
o how to seek a lock: the issue of persistency
o how to resolve a failed lock: the issue of response

The discussion below refers to the use of files in the shared mode.
Note that the concept of locking is not relevant outside this
context and the lock functions are not intended for use with files
open in the exclusive mode.


The Mechanics of Locking

Let's review the essentials of network data access in Clipper.
Exclusive control results from use of the command-like lock
functions, FLOCK() and RLOCK().  The lock functions both perform
the locking attempt and report back the result (true or false).
Suppose user 1's code contains a lock function that executes
successfully (returns .T.).

o What data is affected?

In the case of an FLOCK(), the entire file in the current select
area.  In the case of RLOCK(), the record that was current to user
1 when he RLOCKed.

o What are the effects?

First, user 1 is now allowed to write.  (Writing to shared unlocked
files normally gets "System error not locked" message.)

Second, other users' attempts to lock the same data fails. There
are no effects on other users' read access attempts.

o  How long do the effects last?

Until user 1 undoes his lock by:

- issuing an UNLOCK in the locked file's select area
- issuing UNLOCK ALL in any select area
- closing the file with USE
- terminating his program normally
- issuing another lock command in that file

Issuing another lock command undoes an existing lock before trying
to achieve the new one.  Thus, any failed lock or a successful
RLOCK() on a different record lifts the original lock.  Record
pointer movement does not remove locks.

Only one lock can be in effect at a time in any file held open by a
user in shared mode.  Each user may have several locks since he can
have several files open; however, in each file he can gave only a
single lock.


How To Open Files

In shared mode there are some special rules for USEing files.
First, of course, you must SET EXCLUSIVE OFF before issuing the USE
command.  Second, always check NETERR() immediately after the USE
attempt. Third, never include the INDEX clause in a USE command;
instead, defer opening the index files until after you check
NETERR(), and use SET INDEX to do so.  Here is code that follows
the new rules:

SET EXCLUSIVE OFF        && later USE will be shared mode
USE file
IF NETERR()              && NETERR() tells us if we USEd OK
  ? 'File not available in shared mode. Program terminated.'
  QUIT
ENDIF
SET INDEX TO ...

or, practically equivalent but preferred:

SET EXCLUSIVE OFF
IF NET_USE("file",.F.,5)  && Recommended network file-USE UDF
  SET INDEX TO ...        &&  found in LOCKS.PRG
ELSE
  ? 'File not available in shared mode. Program terminated.'
  QUIT
ENDIF

Notice (in IF NETERR and IF NET_USE above) we must anticipate the
file's possible unavailability to us (even in the more permissive
shared mode), since another user may have the file open in the
exclusive mode.  His "exclusiveness" means us -- we are the ones
that get "excluded."

If ours is the only application on the network that ever uses the
file, of course, we'll probably never be excluded (since the
application almost never uses files exclusively).  But we should
not rely on that assumption.  What if a curious programmer uses the
file with DOT.PRG someday?  Our program should be able to detect
it.  It is better to take the safe and conservative approach.

Notice too that we always separate SET INDEX from USE.  A failed
open attempt does not disrupt our program if it is a database file,
but disruption occurs if it is an index.  A failed USE <database>
merely set NETERR() true.  But a USE <database> INDEX <index> that
is denied shared use of the index stops us in our tracks with a
disruptive Q/A/I message.

The problem is solved by recognizing that if only the shared-mode
USE <database> succeeds, opening the index is guaranteed to be
successful.  An index file's open mode is automatically and
implicitly dictated by that of the associated database file.  If
our shared mode USE <database> works, any other users of the
database running this application must also be in shared mode. That
mode therefore extends to their use of the index files as well.
Therefore, our shared-mode attempt to open index files with SET
INDEX TO will succeed.


When To Lock

When we open the file in shared mode we are saying we want to let
others use the data in the file.  However, we may want "strings
attached" to that commitment, and temporarily appropriate all or
part of the file to our own private control.  In Clipper, the
"strings attached" to the "open door policy" of shared use takes
the form of file and record locks.

What operations require a lock?  When is it important to deny
others access to data?  The answer varies among dBASE language
products, and is simplest in Clipper's case:

Locking Required -->  Whenever you are going to write to a database

Locking Optional -->  At all other times

The commands that write to database files are REPLACE, DELETE,
RECALL, and @...GET <fieldname>.  Pre-locking is a hard and fast
requirement for these commands.  Clipper enforces it with the error
message "System error not locked" if you execute them without a
record lock.

In other situations locking may be important for logical reasons,
even if it is not required by the software.  The most common case
is file-wide operations that need to portray a "snapshot" of the
file at some instant.  We do not want a blurred immage, so we make
the subject stationary, using a file lock, while we "snap the
shot."

For example, SUMming a field requires a lock if data volatility
would logically invalidate the result (by introducing double
counting for example).  Clipper will take the SUM whether the file
is locked or not, but the result could differ depending if the
field values are allowed to change while SUM is taking the tally.

Another category of operations falls outside this discussion but
should be mentioned.  These are operations that you cannot perform
at all in the shared use setting: PACK, REINDEX, and ZAP.  They
require exclusive use instead.  Clipper enforces this requirement
with the error message "System error not exclusive" if you have not
obtained exclusive use of the file.  (Exclusive use, as opposed to
shared use with a file lock in effect, are not the same thing.)

Certain situations where Clipper requires no lock at all should be
emphasized.  No lock is needed for:

o  any passive operation that only reads the file (e.g., LIST,
REPORT FORM, or APPEND FROM in relation to the source file).

o proper maintenance of shared index files.

o APPENDing records to a shared database (use NETERR; see ADD_REC
function in LOCKS.PRG file).

This stands in contrast to other dBASE language products, which do
impose such requirements.  Similarly, Clipper SEEKs, SKIPs, and
GOes TO locked records without error.


When Not To Lock: Static Locking

An important sub-issue is the timing of locks when user interaction
is involved.  Though not absolutely essential, it is strongly
recommended to avoid "static" locking (locks held while waiting for
user response).  This is the network's "do not block intersection"
rule.

In the typical situation the user is editing a record.  When you
finish, the edited version of the data must be written back into
the record.  The operation could be sequenced two ways:

Sequence 1     lock - read - edit - write - unlock
Sequence 2     read - edit - lock - write - unlock

The problem with Sequence 1 is that there is no limit on the
duration of the lock interval.  It encompasses the whole edit
session.  If the user goes to lunch the record is needlessly denied
to all other users, who may need it, for an extended period.  Even
if the user edits normally, the time it takes is very long by
computer standards. Sequence 2 places the user reponse outside the
lock interval.  It is preferred because it minimizes the impact on
everyone else.

Not all programmers choose to observe the "no static locking" rule.
However, an application that carefully folows it can minimize
waiting-for-locks to the point of elimination.  That is because if
the application only "competes" for files with copies of itself at
other workstations, and not with other maverick or ill-mannered
applications, the programmer controls both ends of the data
contention conflict.  He determines how long the applications
imposes itseld once it has a lock, and how patiently and
persistently it reacts to the imposition while trying to get one.
Make the program sufficiently unselfish (lock intervals short
enough) and patient (persistence intervals long enough), and you
will see noticeable waining practically disappear.

In practice the edit consists of GET/READs and the write is done
with REPLACE.  Notice that Sequence 2 implies that the GET targets
should be memory variables, not fieldnames.  GETting direct to
fieldname conbines the edit and write functions, which must be
separate in Sequence 2.

m_field1 = field1                       && GETting to memvars
m_field2 = field2                       && rather than dbf fields
@ y1,x1 SAY 'Field one: ' GET m_field1  && more important now
@ y2,x2 SAY 'Field two: ' GET m_field2  && with networks than
READ                                    && in single-user

GETing memory variable copies of the fields is not a new technique.
It was recommended in single user days to offer opportunity for
data validation and to protect field contents.  On the network
there is further reason to do it this way.

Although Sequence 2 saves us from static locking, there is a price
to pay.  Once we have the edited memvars ready and the record
locked, we should look at the record again before overwriting it.
During our edit another user may have made changes from another
workstation. Our imminent REPLACE will obliterate his work.  Of
course, one of the purposes of data sharing is precisely to let
different people dynamically alter the same piece of data.  In many
applications this would be acceptable.  But in other applications
you might require that before any write, one checks the data to be
overwritten as being the same data we were given to work with in
the memory variables. This guarantees we know what we are
overwriting.

You achieve this by comparing  what you are about to write with
what you originally read.  If they match, go ahead and write.  But
if not, advise the user of the difference and take some other
course of action.  Possible choices are:

o  go ahead and write anyway (this user prevails)
o  discard the user's changes (other user prevails)
o  place the user's changes in a temporary file for later application
   to the main file
o  do a re-read re-paint re-edit (user takes another crack at the
   data as it now stands)

In order to make a comparison, you must record the state of the
original data when you first read it. There are at least two ways
to do this.  One is to store a second copy of the data at read time
that will remain unedited.  Once you have obtained your record
lock, compare it with the file data as a condition for the
REPLACE.  The other method is to use a separate signature field in
the database as an update marker (see James Buzzard's "Writing
Network Application Programs," Nantucket News Volume 1, No. 3). The
MININET sample application uses a signature field.


Seeking A Lock: The Issue of Persistency

When it comes to laying strategy for getting the lock, an issue I
call "persistency" arises.  How hard are we going to try?  If we
cannot lock what we want, when is throwing in the towel and giving
up premature and when is it appropriate?  Several approaches are
possible:

o  try once
o  retry fixed time
o  retry until interrupted (e.g. Esc key)
o  retry fixed time or until interrupted
o  retry forever

At one end of the spectrum is the "try once" or "lightweight and
useless" approach.  We simply surround all REPLACEs with an IF
RLOCK() construction:

"Try Once" Approach:

IF RLOCK()
  REPLACE field1 WITH m_field1
  REPLACE field2 WITH m_field2
ENDIF

This says forgo the REPLACEs altogether if you cannot lock the
record at the moment you want it.  The REPLACEs will simply never
happen.  This is not a practical choice in most applications.  We
must be more persistent.

The opposite extreme is to retry forever.  We could change the
IF/ENDIF to a DO/ENDDO and rearrange things, or use the user
defined REC_LOCK() function found in the LOCKS.PRG procedure file:


"Retry Forever" Approach:

DO WHILE .NOT.RLOCK()                IF REC_LOCK(0)
  INKEY(.5)                   -OR-      REPLACE field1 WITH m_field1
ENDDO                                   REPLACE field2 WITH m_field2
REPLACE field1 WITH m_field1         ENDIF
REPLACE field2 WITH m_field2

Note: the INKEY(.5) pause is important. It spares the network
needless repetition of the RLOCK() attempt that would otherwise
bottleneck the network cable channel.

This approach is based on brute force.  It ensures that the program
will not proceed until the lock is obtained-- whenever that may
be-- and is not a very adaptive stategy.  Obviously, the program
could be in for a long wait.  For some applications that may be
fine; for most it is not.

If we want our network programs to be flexible and practical, we
need a compromise between giving up too soon and waiting too long.
One way to decide when to terminate the retry effort is to set a
time limit. Another method is to let the user terminate it.  We may
also try a combination of the two.

The lock UDFs REC_LOCK() and FIL_LOCK() in the LOCKS.PRG file
(Clipper Autumn '86) provide for the time limit.  A slight
modification permits user termination.  Put the INKEY(.5) to work
by examining the value it returns.  If it indicates the escape key
has been struck, exit the function:

IF INKEY(.5) = 27
  EXIT
ENDIF

In MININET.PRG the modified function is called REC_LOCKER().  The
code for our other three strategies becomes:

"Retry Fixed Time" Approach:

  REC_LOCK(2)                      && try for 2 seconds

"Retry Till Interrupted" Approach

  REC_LOCKER(0)        && try forever until interruption

"Retry Fixed Time or Until Interrupted" Approach

  REC_LOCKER(2)       && try 2 seconds or until interruption


Resolving A Failed Lock: The Issue of Response

Once we find (despite a persistent effort) that our target data is
unavailable (RLOCK() stays false) it is time for contingency plans.
Here lies the real difference between network code and familiar
single user code.

The code adaptations we have made so far for opening files and
seeking locks are straightforward.  They do not change the
program's flow or function, but just add a few more activities
along the way.  Now however the program has to abandon its original
intentions and come up with substitute plans.

You should do two things at this point:

o communicate the lock failure to the user and
o make a branching choice.

There are many ways to handle the user communication.  You can make
it transient (two seconds duration, for example), or follow it by
an INKEY(0) pause to be sure the user has seen it.  It can be a
small message in a corner of the screen, or a whole screen
sandwiched between SAVE SCREEN/RESTORE SCREEN commands.

The branching can be hard coded so that the program controls it
autonomously, or the program can provide for multiple-way branching
with user intervention at a small menu.  In the latter case, the
menu itself constitutes the user advice.  A typical menu might
offer three choices:

o retry
o try same activity, different data
o back to menu - abort current activity

The first choice amounts to letting the user extend the lock
effort.  It does not really face up to the lock's failure or
genuinely seek to resolve it.

The second choice makes sense in many applications.  If the target
record is not available, use another record instead.  Return to the
original record later and it will probably be free.  For example, a
data entry operator working a stack of credit adjustment slips will
not mind putting Smith's slip at the bottom of the pile and moving
on to Jones and Brown.  When he gets back to Smith an hour later
that record will be free.  Therefore, make the code branch to the
record selection entry point.

The third choice implies abandoning not only the record in
question, but the activity itself.  The user leaves the credit
adjustment section of the program altogether and goes back to the
main menu.

The program can hard-code a single choice, or multiple choices
based on some conditional logic.  If you provide multiple choices,
you can offer them to the user via a menu rather than hard-code
them.  No particular response is sacrosanct.  What is appropriate
depends on the application and your tastes.

Whatever choice you make, do not neglect to UNLOCK as soon as
possible after your lock has served its purpose.  If your response
involves a menu, UNLOCK before you display it.  Otherwise you
violate static locking.  (That's why the "overwrite other user"
option is not on the "locked but changed" menu in MININET.)

Notice that none of these choices require special purpose ON ERROR
type processing code, merely branching control.  The programmer
should not have to make major, wholesale extension to his
application just to adapt it to the network environment.  Clipper's
design does not require or encourage overhauling an application to
run on the network.


Other Tips

This discussion is largely confined to database files and a few
standard commands that operate on these files.  Several other
operations work with one or two open files of various types, and
open them implicitly in some cases.  You should understand which
kind of open (exclusive or shared) applies to each at the operating
system level.

For example, the APPEND FROM <file> command opens the designated
file, extracts the data it requires, then closes the file. This
occurs autonomously and implicitly.  Unless you give it some
thought you may not even be aware of the process.  The general rule
is that such commands open their target files in the shared mode if
they are going to read them, and the exclusive mode if they are
going to write.

Commands That Read From <file>     Commands That Write To
and Open <file> Shared             <file> and Open <file> Exclusive

APPEND FROM <file>                 COPY STRUCTURE TO <file>
CREATE...FROM <file>               COPY TO <file>
UPDATE...FROM <file>...            CREATE <file>
LABEL FORM <file>                  INDEX ON...TO <file>
RESTORE FROM <file>                JOIN...TO <file>...
REPORT FORM <file>                 SORT...TO <file>...
TYPE <file>                        TOTAL...TO <file>
                                   SET ALTERNATE TO <file>
                                   SAVE TO <file>

Do not be confused by the fact that some of these commands work
with two files, often the one named in the above syntax and the
current DBF file.  The table refers only to the named file.  The
current DBF file's open mode is predetermined, under the
programmer's explicit control, and depends on commands already
discussed.

Sometimes it is desirable to generate filenames that uniquely
reflect the identity of the workstation or the user.  Network
operating systems may provide such names.  The IBM(r) PC Network
Program, for example, maintains a unique name for every machine.
The Clipper function NETNAME() returns the workstation's name to a
Clipper application.  You can use it as the basis for a unique
filename. Unfortunately, not all network operating systems
maintains such names.  Novell NetWare(tm), for instance, keeps
unique names for each user but not for each machine.  Moreover, the
user names are not easily accessible to Clipper programs.  NetWare
does provide a way to emulate machine names (put MACHINE NAME= in
each workstation's login script).

You can devise your own name generation scheme for networks where
machine names are not available.  One possibility is to put a
variable containing a unique name in each machine's DOS environment
at boot time (put a SET command in each AUTOEXEC.BAT file).
Clipper applications can retrieve it with the GETE() function
(found in the EXTENDC.C file).  Or keep a kisk file containing a
running number. Incorporate the number in your filename at any
time, then increment and rewrite the number.  You can also use some
kind of pseudo random number genaerator and, just to make sure,
check your proposed name with the FILE() function before finalizing
your choice.

Be careful when applying more than one lock.  Doing this when each
depends on the availability of the other can lead to "deadly
embrace." Two users wait forever, ech in possession of only one of
the requisite pair of locks.

Also, if an update occurs in connection with related updates to
several other files (typical in accounting applications) eliminate
the possibility of user interruption that may leave the fileset in
a state of only partial update.  Use SET ESCAPE OFF and be sure
that no SET KEY routines offer unintended avenuew out of the
routine.


MININET.PRG: A Sample Application

The MININET sample application (download NANNWS14.ARC from the
Nantucket SIG File Library) illustrates the networking techniques I
have presented.  Run it on your network, observe its behavior, and
compare the observed behavior with the published code.  It has a
"plain vanilla" menu for adding record, editing them, examining
them, and conducting filewide maintenance operations.

MININET seeks to illustrate that in Clipper there is no adaptation
necessary for adding and examining records.  However, editing
records requires record level locking and maintenance calls for
file level locks.  The real substance of the application lies in
the edit procedure.  It uses a databas called STATES.DBF containing
  the abbreviations, names, and capitals of the 13 original
American states.  You may add any of the other 37.  At any time you
can peek at the file's contents to see the program's effects by
pressing the F2 key.

MININET, by design, never locks a file or record and holds it
locked.  Therefore, you may want another tool to hold a record
locked in order to see how MININET will react.  NETWORK.PRG and
DOT.PRG are suitable.  Or you may write and compile your own
Clipper routine.  You must not use another dBASE language product,
since the major products on the market today implement locking
differently and will not recognize or respect one another's locks
properly.

If you use the interactive DOT program, do not forget to SET
EXCLUSIVE OFF before USEing STATES.DBF.  GOTO a record of your
choice.  Then lock it by issuing ? RLOCK().  If it return .T. on
your display you have the record locked.  Now step over to a
workstation running MININET and try to edit the same record.

Another test worth trying is to run MININET from two workstations.
Begin to edit the record for a particular state, New York for
example.  Once New York and Albany appear on screen, step over to
the other workstation and coduct an edit session in which you
change Albany to Rochester.  Go back to the first station, try
changing Albany (which is still on that screen) to Schenectady.
Observe what happens when the program checks and sees Rochester,
not Albany, in the database as it prepares to write Schenectady.

Also, go into the maintain option to REPLACE ALL (requires a file
lock), and REINDEX or DELETE FOR/PACK (requires exclusive USE).
Observe the effects when other stations have exclusive use of the
file, and shared use with no lock, a record lock, and a file lock.


Conclusion

Clipper takes a simple and sparing approach to implementing
networking commands and functions.  It introduces as little
complexity as possible into application programming for network
environments, while leaving the power to control network
applications in the dBASE language products.  With the growth of
networking applications in the dBASE language, more programmers
will find Clipper's implementation advantageous.

--End: NETWORKING WITH CLIPPER


TOPIC:  RLOCK() AND FLOCK()
AUTHOR:  DAVID MORGAN
DATE:  21 Nov 1986
TEXT:
--------------------------------------------------------------------
The "Hybrids" RLOCK() and FLOCK():  Functions or Procedures?
David Morgan
--------------------------------------------------------------------

Technicians in Nantucket's support department are discovering that
a bit of re-education is called for in learning Clipper's
networking features.  The main adjustment is becoming conscious of
other users and, like an only child meeting his first sibling,
learning to share.  Data sharing is the central purpose not only of
multi-user Clipper, but of networks in general.

A good example is offered by the new locking functions FLOCK() and
RLOCK().  They are at the heart of effective data sharing in
Clipper.  And they have an unusual feature that requires an
additional mental readjustment.  Namely, though technically
functions, they operate like hybrids, halfway between a pure
function and a procedure.

FLOCK() and RLOCK() are the tools for achieving private control of
files and records during critical operations.  Operations like
writing new data to a record, or deriving composite results for the
file.  Without freezing other users out at these critical times,
bad things would happen.  For example, simultaneous writes by
different users would result in collisions that could leave garbage
in the target record.  Or, since the data would be volatile,
operations like totalling certain fields over the whole file could
end up reflecting double counting.  So we try to put a lock on the
file or record before performing any critical operation and, if not
successful, we desist.  Some of the programming techniques for
approaching these situations will be the topic of a future short
article.

Here I want to look at the lock functions as crossbreeds between
true functions and procedures.  You have to get used to this idea
since the two aspects of these functions are available only in
combined form, not separately.

By ideal definition, a function is something that simply returns a
value.  Like a function in algegra, that's its only job.  You feed
it one or more quantities as inputs.  The function is a black box.
In go the input quantities.  After they disappear from sight, a
single output quantity emerges.  It is supplied to the caller of
the function, and the function's job is done.  x goes into f() and
out comes f(x).  The purpose of calling the function is to get the
value it produces.  By contrast, a procedure is defined by the
actions it performs.  The purpose of calling the procedure is to
trigger those actions.  Two examples in Clipper appear below:

   Example of a Function            Example of a Procedure

   * MAIN PROGRAM                   * MAIN PROGRAM
   two = 2                          bell = CHR(7)
   ?  "Two times "                  ? "Ask not for whom the bell "
   ?? two                           DO ring_bell
   ?? " equals "                    ?? "tolls."
   ?? two_times(2)

   FUNCTION two_times               PROCEDURE ring_bell
   PARAMETERS some_value            ?? bell
   RETURN (2 * some_value)          RETURN


In the first example we need the value of two times two.  We call
the function because it will supply it.  In the second we want the
bell in the computer to ring.  We call the procedure because that's
what it will do.

To reinforce, look at the two RETURN statements.  They reflect the
distinction.  The first one says "give back," the second simply "go
back."  In fact the parentheses, containing the value to give back,
are required in the RETURN syntax of a function but prohibited in
that of a procedure.  (See also the sample code in the Clipper
manual, p. 4-30.)

The central distinction applies beyond user-defined routines you
yourself write.  The keywords in the dBASE language itself, which
break down into functions and commands, call lower level routines
which are also functions and procedures, respectively, in the above
sense.  When you use functions, such as LEN(), EOF(), or MAX(),
your interest lies in the values they return.  You don't care what
they do internally to get them.  You just want the facts, please.

Information, not action.  When you use commands, such as CLEAR,
EJECT, or REPLACE, you are interested in accomplishing the actions
they perform.  EJECT returns no information.  So it's like a
procedure.  Action, not information.

Real world functions and procedures aren't quite so ideal.
Functions can contain code that does things apart from determining
the value to return.  Procedures might assign values to public
variables and thus indirectly return information to the caller. But
in general, functions are useful for the information they provide
while procedures are useful for the things they do.

So where does all this leave RLOCK() and FLOCK()?  Well, first of
all they're functions.  Just look at those parentheses.  They
return logical information about the current record or file.  If
they return "true" it's locked, if "false" it isn't.  This is
important information.  We need it whenever we have in mind an
operation that requires private control of the data.  We use the
information to decide whether it's safe to go ahead with the
operation.  RLOCK() and FLOCK() are our querying tools.  Example:

   RLOCK() as a function

   IF RLOCK()
     <operate on the current record>
   ELSE
     <not safe to do so now, give the user a message instead>
   ENDIF

But these functions have a second, less visible role.  They are the
locking agents in the dBASE language.  They try to apply the lock
each time they are executed.  This is procedure-like, in contrast
to the function-like role performed above.  What's not apparent
above is that the RLOCK() itself tries to seize the record and only
then reports the status.  So even if the record weren't locked
going into the IF statement, RLOCK() might still return true,
because it could change the record from unlocked to locked during
its own operation.  In fact there's no way to know the lock status
before the IF statement (unless we have kept track of the results
of previous locking activity).  Our only candidate  tool for
finding out is RLOCK().  But it's disqualified because it
influences the status in the process of evaluating it.  Like
Heisenberg uncertainty in physics, the measurer disturbs the
measured, making measurement impossible.  This isn't a problem.
It's a new circumstance that just takes a little getting used to.

The loop below demonstrates the procedure-like aspect of RLOCK()'s
dual personality:

   RLOCK() as a procedure

   DO WHILE .NOT.RLOCK()
   ENDDO

At first glance it looks like there's code missing from within the
loop, as in:

   DO WHILE .NOT.EOF()      instead of       DO WHILE .NOT.EOF()
   ENDDO                                        SKIP
                                              ENDDO

But that's not the case.  RLOCK() is different from EOF().  EOF()
is a pure function.  It only tells you if you are or aren't at the
end of file, but it doesn't actively do anything about it.  That
why SKIP is necessary.  Without it the loop goes on forever because
there is nothing to change the status of EOF().

By contrast, RLOCK() isn't a pure function.  It actively tries to
lock the current record every time through the loop.  If another
workstation denies it success by holding the same record locked, it
keeps looping.  But no sooner does the other workstation UNLOCK the
record than RLOCK(), the procedure, gets the record locked, so that
RLOCK(), the function, returns true.  At that point the loop is
exited and processing can continue.  So, though not the best
programming technique, the RLOCK() loop above makes sense.  It says
"wait until the record can be locked from this workstation, then
proceed."  RLOCK() performs the active role of a procedure, like
SKIP, as well as the passive one of a function, like EOF().

To familiarize yourself further with RLOCK() and FLOCK(), look at
the code in the LOCKS.PRG file on your Autumn '86 PLINK86 and
Utilities disk.  The same code also appears in the Autumn '86
Supplement; see pp. III-16 and III-17.  These are the user defined
functions we recommend you utilize to lock files and records.  See
also the program NETWORK.PRG on the same diskette.  Compile and run
it on your network from two or more workstations.  It helps
illuminate what happens when workstations try to share data.
Remember that these functions are ignored if you are running on a
stand-alone machine.  You must be on a network to actually observe
RLOCK() and FLOCK() at work.  A future article will discuss
programming techniques for data sharing.

--End: RLOCK() AND FLOCK()
