Level Extreme platform
Subscription
Corporate profile
Products & Services
Support
Legal
Français
Terminal Services TABLEUPDATE Failure
Message
From
18/09/2005 10:19:51
 
General information
Forum:
Visual FoxPro
Category:
Client/server
Environment versions
Visual FoxPro:
VFP 7 SP1
OS:
Windows XP SP2
Network:
Windows 2000 Server
Database:
Visual FoxPro
Miscellaneous
Thread ID:
01045421
Message ID:
01050697
Views:
46
>One workaround in your case could be to lock the header with RLOCK(0) before you try to update. By looping until you can place a lock on the header you should be able to serialize the TABLEUPDATE() calls letting only one user at a time save new records.

This is going to be a lengthy post; my apologies in advance.
A quick reprise of the problem: two users who save simultaneously in Terminal Services sessions running
concurrently on the same server trigger a TABLEUPDATE failure. Subsequent AERROR call shows
ERROR 109, "Record is in use by another user." Optimistic table buffering is being used.

Here's the set up:
All of my data entry forms are based on a form class that has a SaveChanges method. It loops
through the form DE and makes a list of dirty buffers then loops through that list attempting
a TABLEUPDATE (not forced) for each. If that fails, a Resolve method is called which checks for
field collisions--none are found in the problem scenario--then issues a forced TABLEUPDATE.

At the suggestion of Microsoft Tech Support, I broke the TABLEUPDATE calls out into a separate method
so I could set up a retry loop. Here's the code described so far:
***********************************
*- thisform.SaveChanges pseudo-code
***********************************
Loop through all members of DE getting a list of dirty buffers into array laBuffers

llLogging = .T. && or .F.; <user has turned on logging function>
llRollBack = .F. && initialized for clarity

BEGIN TRANSACTION 
	
Loop through all members of dirty buffers array laBuffers
	IF NOT thisform.MyTableUpdate(1,.F.,<dirty buffer alias>,llLogging,1)
		llRollBack = NOT thisform.Resolve(<dirty buffer name>)
	ENDIF
END loop

IF llRollBack
	ROLLBACK
	thisform.DoRevert() && TABLEREVERT() all dirty buffers
ELSE
	END TRANSACTION 
ENDIF

RETURN NOT llRollBack

*******************************
*- thisform.Resolve pseudo-code
*******************************
USE AFIELDS() to loop through all fields checking for field collisions
	IF any found, give user an option to manually resolve
	*- in current problem, there are NO field collisons
	ENDIF
endloop

*- now force a table update
RETURN thisform.MyTableUpdate(0,.T.,<dirty buffer alias>,llLogging,3)

*************************************
*- Actual thisform.MyTableUpdate Code
*************************************
LPARAMETERS tnRows,tlForce,tcTableAlias,tlLogging,tnCalledFrom

LOCAL ;
	lnCount,;
	llReturn,; 
	laErrorArray[1],; 
	lcMessage,; 
	lnI,;
	lnMax,;
	lnDelay,;
	lnFlush
	
WITH thisform
	lnMax = .nMaxTries && set at 10 by default
	lnDelay = .nDelay && set at 0.05 by default
ENDWITH
	
lnCount = 0

DO WHILE lnCount < lnMax AND !llReturn
	llReturn = TABLEUPDATE(tnRows,tlForce,tcTableAlias,'laErrorArray')
	lnFlush = SYS(1104) && this added at Microsoft Tech Support's suggestion
	
	IF !llReturn
		IF tlLogging 
			IF lnCount = 0
			   = AERROR(laErrorArray)  && Data from most recent error
				lcMessage = 'Source = ' + TRANSFORM(tnCalledFrom) + '; flush = ' + TRANSFORM(lnFlush) + '; '
				
			   FOR lnI = 1 TO 7  && Display all elements of the error array
			   	lcMessage = lcMessage + TRANSFORM(lnI) + ': ' + TRANSFORM(laErrorArray(lnI)) + CRLF
			   ENDFOR
			ELSE
				lcMessage = 'Attempt # ' + TRANSFORM(lnCount) + ', delay = ' + TRANSFORM(lnDelay) + ', flush = ' + TRANSFORM(lnFlush)
			ENDIF 

	           oLogger.LogMileStone('TABLEUPDATE failed: ' + tcTableAlias + ', ' + lcMessage)
		ENDIF
			
		lnCount = lnCount + 1 
		=INKEY(lnDelay,'HM')
	ENDIF 
ENDDO

RETURN llReturn
The above version of the MyTableUpdate() code solves the problem on my development network,
which consists of a server running two Terminal Services sessions initiated from separate side-by-side work
stations where I can force simultaneous saves. At my customer's site, however, this code still produces
a TABLEUPDATE failure. I have given them the ability to change the lnMax and lnDelay values on
the fly, and they will be testing different (higher) values this week.

Meanwhile, I have tried my own variant using FLOCK and Christof's suggestion using RLOCK(0)
WITH slight variations on the revised code for MyTableUpdate shown below. In both cases, one user
"wins" and saves successfully, meaning the UNLOCK is executed, but the other user, without a
counter, goes into an infinite loop in the DO WHILE, FLOCK/RLOCK loop. I added the counter so
that the loop is exited eventually, but in both cases, the "losing" user can never establish
a lock, and the subsequent TABLEUPDATE fails with the same error as before. I am at a loss, can somebody
suggest a better solution?
*- Revised thisform.MyTableUpdate code
LPARAMETERS tnRows,tlForce,tcTableAlias,tlLogging,tnCalledFrom

LOCAL ;
	lnCount,;
	llReturn,; 
	laErrorArray[1],; 
	lcMessage,; 
	lnI,;
	lnMax,;
	lnDelay,;
	lnFlush,;
	lnOldSetReprocess
	
WITH thisform
	lnMax = .nMaxTries && set at 10 by default
	lnDelay = .nDelay && set at 0.05 by default
ENDWITH
	
lnOldSetReprocess = SET('REPROCESS')
SET REPROCESS TO 1
lnCount = 0

DO WHILE lnCount < lnMax AND !RLOCK('0',tcTableAlias)
	WAIT 'Save Changes waiting for lock' WINDOW TIMEOUT 1
	lnCount = lnCount + 1 
ENDDO 
	
llReturn = TABLEUPDATE(tnRows,tlForce,tcTableAlias,'laErrorArray')
UNLOCK ALL && also tried UNLOCK RECORD 0 here, no difference
lnFlush = SYS(1104)
	
IF !llReturn AND tlLogging 
   = AERROR(laErrorArray)  && Data from most recent error
	lcMessage = 'Source = ' + TRANSFORM(tnCalledFrom) + '; flush = ' + TRANSFORM(lnFlush) + '; '
	
   FOR lnI = 1 TO 7  && Display all elements of the error array
   	lcMessage = lcMessage + TRANSFORM(lnI) + ': ' + TRANSFORM(laErrorArray(lnI)) + CRLF
   ENDFOR

   oLogger.LogMileStone('TABLEUPDATE failed: ' + tcTableAlias + ', ' + lcMessage)
ENDIF 

SET REPROCESS TO (lnOldSetReprocess)
RETURN llReturn
Ray Roper
Previous
Next
Reply
Map
View

Click here to load this message in the networking platform