From b24277dd1bcbb217f884a7abd5d58f6c1038f6ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Toni=20K=C3=B6hler?= <44064940+ToniKoehler@users.noreply.github.com> Date: Sat, 8 Nov 2025 22:23:32 +0100 Subject: [PATCH 1/3] parallelfox.vca based on branch v2.0 parallelfox.vca from original VFPX repository is not proper version to parallelfox.vcx/.vct, so first create correct .vca with SourceSafe --- parallelfox.vca | 352 ++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 310 insertions(+), 42 deletions(-) diff --git a/parallelfox.vca b/parallelfox.vca index c58ca7f..988eb54 100644 --- a/parallelfox.vca +++ b/parallelfox.vca @@ -79,7 +79,7 @@ Local lnParameter, lcParameter, loParameters as Parameters of ParallelFox.vcx, l Debugout Time(0), Program() -loParameters = NewObject("Parameters","ParallelFox.vcx") +loParameters = NewObject("Parameters",This.ClassLibrary) loParameters.nPCount = lnPCount * AddProperty(loParameters, "nPCount", lnPCount) @@ -183,6 +183,57 @@ Pixels[END RESERVED6] [UNIQUEID] RESERVED [OBJNAME] command +[ RECORD] +[PLATFORM] WINDOWS +[UNIQUEID] _6NX0WQ2U6 +[CLASS] custom +[BASECLASS] custom +[OBJNAME] commandhandler +[START PROPERTIES] +Height = 16 +Name = "commandhandler" +Width = 100 +_memberdata = +ccommand = +[END PROPERTIES] +[START METHODS] +PROCEDURE Destroy +Debugout Time(0), Program() + +ENDPROC +PROCEDURE executecommand +* Simple event handler to execute one-line command when event fires. +* Used by Parallel.BindEvent(). +Lparameters tPar1, tPar2, tPar3, tPar4, tPar5, tPar6, ; + tPar7, tPar8, tPar9, tPar10, tPar11, tPar12, tPar13, ; + tPar14, tPar15, tPar16, tPar17, tPar18, tPar19, tPar20, ; + tPar21, tPar22, tPar23, tPar24, tPar25, tPar26 +Local lcCommmand + +Debugout Time(0), Program() + +lcCommand = This.cCommand +&lcCommand + +ENDPROC +[END METHODS] +[START RESERVED1] +Class[END RESERVED1] +[START RESERVED2] +1[END RESERVED2] +[START RESERVED3] +*executecommand Run command when event fires. +_memberdata XML Metadata for customizable properties +ccommand Command to execute when event fires. +[END RESERVED3] +[START RESERVED6] +Pixels[END RESERVED6] + +[ RECORD] +[PLATFORM] COMMENT +[UNIQUEID] RESERVED +[OBJNAME] commandhandler + [ RECORD] [PLATFORM] WINDOWS [UNIQUEID] _3CV0I1GR4 @@ -206,7 +257,7 @@ Lparameters lnError, lcMethod, lnLine, lcMessage, lcCode Local array laCallStack[1,6] Local lnRow, lnErrorRow, lcCallStack Local Worker as Worker -Worker = NewObject("Worker", "ParallelFox.vcx") +Worker = NewObject("Worker", This.ClassLibrary) * Add call stack to lcCode AStackInfo(laCallStack) @@ -259,12 +310,23 @@ Pixels[END RESERVED6] [BASECLASS] custom [OBJNAME] events [START PROPERTIES] - Name = "events" -_memberdata =  581 +_memberdata =  683 +iscancelled = .F. ncommands = 0 +ohandlers = .NULL. [END PROPERTIES] [START METHODS] +PROCEDURE Destroy +Debugout Time(0), Program() + +If !IsNull(This.oHandlers) and This.oHandlers.Count > 0 + This.oHandlers.Remove(-1) && remove all simple event handlers from collection +EndIf +ENDPROC +PROCEDURE Init +This.oHandlers = CreateObject("Collection") +ENDPROC PROCEDURE complete Lparameters lvReturn @@ -377,7 +439,9 @@ Class[END RESERVED1] *updatecommandcount Update number of commands currently running. *updateprogress Fires when Worker.UpdateProgress() is called on worker. _memberdata XML Metadata for customizable properties +iscancelled Is set to .T. when cancellation is requested by main process. ncommands Number of commands currently queued or running. +ohandlers Collection to keep event handlers in scope. [END RESERVED3] [START RESERVED6] Pixels[END RESERVED6] @@ -540,7 +604,7 @@ Pixels[END RESERVED6] [START PROPERTIES] Name = "parallel" _events = NULL -_memberdata =  997 +_memberdata =  1223 cpucount = 0 oparpoolmgr = .NULL. [END PROPERTIES] @@ -552,7 +616,6 @@ PROCEDURE Destroy Debugout Time(0), Program() UnBindEvents(This) - ENDPROC PROCEDURE Init * Instantiate default parallel pool manager @@ -560,13 +623,14 @@ This.SetInstance() This.CPUCount = This.oParPoolMgr.nCPUCount -This._Events = NewObject("Events", "ParallelFox.vcx") +This._Events = NewObject("Events", This.ClassLibrary) This.BindEvent("ReturnError", This.oParPoolMgr, "HandleError") ENDPROC PROCEDURE bindevent * Bind to worker events -Lparameters cEvent, oEventHandler, cDelegate, nFlags +Lparameters cEvent, oEventHandlerOrCommand, cDelegate, nFlags +Local loCommandHandler as CommandHandler of ParallelFox.vcx Debugout Time(0), Program(), cEvent, cDelegate @@ -578,7 +642,15 @@ If Upper(cEvent) = "RETURNERROR" UnBindEvents(This._Events, "ReturnError", This.oParPoolMgr, "HandleError") EndIf -BindEvent(This._Events, cEvent, oEventHandler, cDelegate, nFlags) +If Vartype(oEventHandlerOrCommand) = "C" + * Use simple handler to execute one line of code when event fires + loCommandHandler = NewObject("CommandHandler", This.ClassLibrary) + loCommandHandler.cCommand = oEventHandlerOrCommand + BindEvent(This._Events, cEvent, loCommandHandler, "ExecuteCommand", nFlags) + This._Events.oHandlers.Add(loCommandHandler) && keep handler in scope +Else + BindEvent(This._Events, cEvent, oEventHandlerOrCommand, cDelegate, nFlags) +EndIf ENDPROC @@ -631,6 +703,13 @@ loParameters = This.oParPoolMgr.CreateParameterObject(Pcount()-5, @tPar1, @tPar2 This.oParPoolMgr.QueueCommand("CallMethod", cMethod, cClassName, cModule, cInApplication, ; loParameters, lAllWorkers, This._Events) +ENDPROC +PROCEDURE cancel +* Clear remaining queue and set IsCancelled property for all workers + +Debugout Time(0), Program() + +This.oParPoolMgr.Cancel() ENDPROC PROCEDURE clearqueue * Remove all pending commands from queue. @@ -638,6 +717,50 @@ PROCEDURE clearqueue Debugout Time(0), Program() This.oParPoolMgr.ClearQueue() +ENDPROC +PROCEDURE createtemptable +* Create temporary table that can be used by workers with Worker.OpenTempTable() +Lparameters cAlias +Local lcTempTable + +cAlias = Evl(cAlias, Alias()) + +Do while .t. + lcTempTable = Addbs(Sys(2023)) + Alltrim(cAlias) + Sys(2015) + "_" + Transform(_VFP.ThreadId) + If !File(lcTempTable + ".DBC") and !File(lcTempTable + ".DBF") + Exit && filename is good + EndIf +EndDo + +* Could have long field names in cursor, so create database +lcDBC = Dbc() +Create Database (lcTempTable + ".DBC") + +Select (cAlias) +Copy To (lcTempTable) DATABASE (lcTempTable) + +* Database is opened exclusively, so close it +Set Database To (lcTempTable) +Close Databases +Set Database To (lcDBC) + +Return JustFname(lcTempTable) +ENDPROC +PROCEDURE deletetemptable +* Delete temporary table previously created with CreateTempTable() +* Make sure Worker.CloseTempTable() is used to close table/DBC in workers before deleting +Lparameters cTempTable +Local lcSafety, lcTempFiles + +lcSafety = Set("Safety") +Try + Set Safety Off + lcTempFiles = Addbs(Sys(2023)) + Alltrim(cTempTable) + ".*" + Erase (lcTempFiles) +Finally + Set Safety &lcSafety +EndTry + ENDPROC PROCEDURE detecthyperthreading * Returns .T. when HyperThreading is Enabled. @@ -727,7 +850,7 @@ EndIf If _Screen.ParPoolMgrs.GetKey(cInstanceName) > 0 This.oParPoolMgr = _Screen.ParPoolMgrs.Item(cInstanceName) Else - This.oParPoolMgr = NewObject("ParPoolMgr", "ParallelFox.vcx", "", cInstanceName) + This.oParPoolMgr = NewObject("ParPoolMgr", This.ClassLibrary, "", cInstanceName) _Screen.ParPoolMgrs.Add(This.oParPoolMgr, cInstanceName) EndIf @@ -739,6 +862,17 @@ Lparameters lMTDLL This.oParPoolMgr.lMTDLL = lMTDLL +ENDPROC +PROCEDURE setregfreecom +* Set .T. to use out-of-process COM EXE without requiring registration. +* Does not apply to debug mode or in-process MTDLL. +* Include path to EXE if not in current path +Lparameters lRegFreeCOM, cRegFreePath + +This.oParPoolMgr.lRegFreeCOM = lRegFreeCOM +If !Empty(cRegFreePath) + This.cRegFreePath = cRegFreePath +EndIf ENDPROC PROCEDURE setworkerclass * Change worker class from default. lcClass and lcLibrary are used in debug mode. @@ -828,7 +962,10 @@ Class[END RESERVED1] *bindevent Bind to worker events: "Complete", "UpdateProgress", "ReturnData", "ReturnError". *call Execute/call function on worker. *callmethod Execute/call class method on worker. +*cancel Clear remaining queue and set IsCancelled property for all workers. *clearqueue Remove all pending commands from queue. +*createtemptable Create temporary table that can be used by workers with Worker.OpenTempTable(). +*deletetemptable Delete temporary table previously created with CreateTempTable(). Make sure Worker.CloseTempTable() is used to close table/DBC in workers. *detecthyperthreading Returns .T. when HyperThreading is enabled. *do Execute program on worker. *docmd Execute single command on worker. @@ -836,6 +973,7 @@ Class[END RESERVED1] *onerror Set up global handler for worker errors. Available variables are nError, cMethod, nLine, cMessage, cCode. Example: Parallel.OnError("Do MyErrorHandler with nError, cMethod, nLine, cMessage, cCode") *setinstance Set instance of parallel pool manager, creating new instance if necessary. *setmultithreaded Set .T. to use in-process multithreaded DLL workers. Otherwise, out-of-process EXEs are used. +*setregfreecom Set .T. to use out-of-process COM EXE without requiring registration. Does not apply to debug mode or in-process MTDLL. *setworkerclass Change worker class from default. lcClass and lcLibrary are used in debug mode. *setworkercount Set number of workers. Defaults to CPU count. Set before starting workers. *startworkers Start worker processes. @@ -919,15 +1057,17 @@ Pixels[END RESERVED6] [OBJNAME] parpoolmgr [START PROPERTIES] Name = "parpoolmgr" -_memberdata =  1217 +_memberdata =  1371 cinstancename = commandqueue = conerror = This.DisplayErrors(nError, cMethod, nLine, cMessage, cCode) +cregfreepath = cworkerclass = WorkerMgr cworkercomprogid = ParallelFox.WorkerMgr cworkerlibrary = ParallelFox.vcx ldebugmode = .F. lmtdll = .F. +lregfreecom = .F. nbusyworkers = 0 ncpucount = 0 nprocessing = 0 @@ -953,6 +1093,18 @@ This.CommandQueue = CreateObject("Collection") Return DoDefault() ENDPROC +PROCEDURE cancel +* Clear remaining queue and set IsCancelled property for all workers +Local loWorkerProxy as WorkerProxy of ParallelFox.vcx + +Debugout Time(0), Program() + +This.ClearQueue() + +For each loWorkerProxy in This.Workers FoxObject + loWorkerProxy.oEvents.IsCancelled = .t. +EndFor +ENDPROC PROCEDURE clearqueue * Remove all pending commands from queue. @@ -968,7 +1120,7 @@ PROCEDURE displayerrors Lparameters lnError, lcMethod, lnLine, lcMessage, lcCode If Vartype(This.oErrorList) <> "O" or IsNull(This.oErrorList) - This.oErrorList = NewObject("frmErrorList", "ParallelFox.vcx") + This.oErrorList = NewObject("frmErrorList", This.ClassLibrary) This.oErrorList.Show() EndIf @@ -1005,7 +1157,6 @@ EndIf * Prevent error if run in the middle of CLEAR ALL If Type("This.Workers.Count") = "U" or Type("This.CommandQueue.Count") = "U" - ? "Exiting ProcessQueue" Return EndIf @@ -1121,7 +1272,7 @@ EndIf For lnWorker = 1 to lnWorkerCnt * Create command object - loCommand = NewObject("Command", "ParallelFox.vcx") + loCommand = NewObject("Command", This.ClassLibrary) loCommand.cCommandType = Evl(lcCommandType, "") loCommand.cCommand = Evl(lcCommand, "") loCommand.cClass = Evl(lcClass, "") @@ -1161,7 +1312,7 @@ PROCEDURE startworkers * Start worker processes * Same EXE is used for all workers Lparameters lcProcedureFile, lcDirectory, llDebugMode -Local lnWorker, loWorkerProxy as WorkerProxy of ParallelFox.vcx +Local lnWorker, loWorkerProxy as WorkerProxy of ParallelFox.vcx, lcWOnTop Debugout Time(0), Program(), lcProcedureFile, lcDirectory, llDebugMode @@ -1172,12 +1323,19 @@ EndIf This.lDebugMode = llDebugMode +lcWOnTop = Wontop() + For lnWorker = 1 to This.nWorkerCount - loWorkerProxy = NewObject("WorkerProxy", "ParallelFox.vcx", "", lcProcedureFile, lcDirectory, llDebugMode, lnWorker, This.lMTDLL, This) + loWorkerProxy = NewObject("WorkerProxy", This.ClassLibrary, "", lcProcedureFile, lcDirectory, llDebugMode, lnWorker, This.lMTDLL, This) ComArray(loWorkerProxy, 11) This.Workers.Add(loWorkerProxy) EndFor +* Restore active window if focus was lost when workers started +If !(Wontop() == lcWOnTop) + Activate Window (lcWOnTop) +EndIf + ENDPROC PROCEDURE stopworkers * Stop worker processes @@ -1204,6 +1362,7 @@ Class[END RESERVED1] [START RESERVED2] 1[END RESERVED2] [START RESERVED3] +*cancel Clear remaining queue and set IsCancelled property for all workers. *clearqueue Remove all pending commands from queue. *displayerrors Default error handler displays list of errors from workers. *handleerror Global Error Handler. @@ -1215,11 +1374,13 @@ Class[END RESERVED1] cinstancename Name of current pool manager instance. commandqueue Command queue collection. conerror On Error command. +cregfreepath Path to registration-free COM EXE if not in current path. cworkerclass Worker class name (used in debug mode). cworkercomprogid COM ProgID for worker class. cworkerlibrary Worker class library (used in debug mode). ldebugmode Start workers in Debug mode. lmtdll Set .T. to use in-process multithreaded DLL workers. Otherwise, out-of-process EXEs are used. +lregfreecom Set .T. to use out-of-process COM EXE without requiring registration. Does not apply to debug mode or in-process MTDLL. nbusyworkers Number of workers currently processing commands. ncpucount Number of logical processors on machine. nprocessing Number of times ProcessQueue has been called. @@ -1364,6 +1525,7 @@ ENDPROC PROCEDURE startthread * Start thread for MTDLL worker Lparameters lcDirectory, lcWorkerClass, lcWorkerVCX, lcWorkerApp, lcProcedureFile +lcProcedureFile = EVL(lcProcedureFile, "") * Start startup script for MTDLL worker Text to This.cMTScript TEXTMERGE NOSHOW @@ -1511,7 +1673,7 @@ Pixels[END RESERVED6] [START PROPERTIES] Name = "worker" _lastprogressupdate = 0 -_memberdata =  719 +_memberdata =  891 cpucount = 0 progressinterval = 1 [END PROPERTIES] @@ -1555,6 +1717,25 @@ DECLARE INTEGER ReleaseMutex IN kernel32; INTEGER hMutex +ENDPROC +PROCEDURE closetemptable +* Close temporary table and associated DBC +Lparameters cAlias +Local lnCurrentArea, lcCurrentDBC, lcDBC + +If Used(cAlias) + lnCurrentArea = Select() + lcCurrentDBC = Dbc() + Select (cAlias) + lcDBC = CursorGetProp("Database") + Use + Set Database To (lcDBC) + Close Databases + Set Database To (lcCurrentDBC) + Select (lnCurrentArea) +EndIf + + ENDPROC PROCEDURE endcriticalsection * End Critical Section of code. @@ -1573,10 +1754,25 @@ If lnRow <> 0 Adel(This._CriticalSections, lnRow) EndIf ENDPROC +PROCEDURE iscancelled +* Returns .T. if cancellation requested in main process + +Return _Screen.oWorkerEvents.IsCancelled +ENDPROC PROCEDURE isworker * Returns .T. if currently running in Worker process. Return Type("_Screen.oWorkerEvents") = "O" +ENDPROC +PROCEDURE opentemptable +* Open temporary table created in main process +Lparameters cTempTable, cAlias + +cAlias = Evl(cAlias, cTempTable) + +Select 0 +Use (Addbs(Sys(2023)) + cTempTable) Alias (cAlias) Shared + ENDPROC PROCEDURE returncursor * Return cursor to main process. @@ -1722,8 +1918,11 @@ Class[END RESERVED1] [START RESERVED2] 1[END RESERVED2] [START RESERVED3] +*closetemptable Close temporary table and associated DBC. *endcriticalsection End Critical Section of code. +*iscancelled Returns .T. if cancellation requested in main process. *isworker Returns .T. if currently running in Worker process. +*opentemptable Open temporary table created in main process. *returncursor Return cursor to main process. *returndata Return data to main process. Due to limitations with VFP BindEvent(), arrays are not supported and cannot be returned. *returnerror Return error to main process. @@ -1775,17 +1974,19 @@ EndIf ENDPROC PROCEDURE Init _VFP.Caption = "ParallelFox Worker" +* Make sure ParallelFox.vcx can be found in workers +Set Path To (JustPath(This.ClassLibrary)) Additive * Set Unattended mode unless in debug mode * Make sure an error handler is in place on worker or displaying UI * can cause worker to crash. If _VFP.StartMode > 1 Sys(2335,0) * Default error handler - _Screen.NewObject("oErrorHandler", "ErrorHandler", "ParallelFox.vcx") + _Screen.NewObject("oErrorHandler", "ErrorHandler", This.ClassLibrary) EndIf Set TablePrompt Off If _VFP.StartMode <> 5 - This.oCmdTimer = NewObject("tmrCommand", "ParallelFox.vcx") + This.oCmdTimer = NewObject("tmrCommand", This.ClassLibrary) EndIf Return DoDefault() @@ -2058,7 +2259,7 @@ Pixels[END RESERVED6] Height = 16 Name = "workerproxy" Width = 99 -_memberdata =  775 +_memberdata =  831 lbusy = .F. lcomplete = .F. ldebugmode = .F. @@ -2091,24 +2292,50 @@ EndIf ENDPROC PROCEDURE Init * Create worker process -Lparameters lcProcedureFile, lcDirectory, llDebugMode, lnWorkerNum, llMTDLL, loParPoolMgr +Lparameters lcProcedureFile, lcDirectory, llDebugMode, lnWorkerNum, llMTDLL, loParPoolMgr, ; + lcParallelFoxProgID, lcRegFreeClassID, lcRegFreeParallelFox, lcVFPProgID, lcMTDLLProgID Local lhWndForeground Local loVFP as VisualFoxPro.Application, lcWorkerVCX, lcWorkerAPP, lcWorkerCmd Debugout Time(0), "(" + Transform(lnWorkerNum) + ")", Program(), lcProcedureFile, lcDirectory, llDebugMode, llMTDLL +#DEFINE PARALLELFOX_CLSID "{76DE0CE0-CE45-491B-9EDF-6F91CDBD9880}" +#DEFINE PARALLELFOXA_CLSID "{2080588E-D21B-4ACE-A1DF-761CF2A296D5}" +#DEFINE PARALLELFOX64_CLSID "{EF323FE2-C5E2-448B-A6A6-0BDE249849D3}" + This.lMTDLL = llMTDLL This.oParPoolMgr = loParPoolMgr -* In Windows XP and earlier, main process can lose focus when -* instantiating COM EXE. Make sure we keep it. -DECLARE INTEGER GetForegroundWindow IN user32 -lhWndForeground = GetForegroundWindow() - +Do Case +Case Version(5) >= 1000 and _Win64 = .t. && VFP Advanced 64-bit + * VFPA64 workers crash, probably when calling back into main process. More testing required. + ERROR "ParallelFox is not functional with VFP Advanced 64-bit version." + lcVFPProgID = "VisualFoxpro.Application.a" + lcParallelFoxProgID = "ParallelFox64.Application" + lcRegFreeParallelFox = Evl(This.oParPoolMgr.cRegFreePath, "ParallelFox64.exe") + lcRegFreeClassID = PARALLELFOX64_CLSID + * MTDLL crashes due to problems with ExecScript(), so use VFP9 version for now +* lcMTDLLProgID = "ParallelFoxMTA.Application" + lcMTDLLProgID = "ParallelFoxMT.Application" +Case Version(5) >= 1000 && VFP Advanced 32-bit + lcVFPProgID = "VisualFoxpro.Application.a" + lcParallelFoxProgID = "ParallelFoxA.Application" + lcRegFreeParallelFox = Evl(This.oParPoolMgr.cRegFreePath, "ParallelFoxA.exe") + lcRegFreeClassID = PARALLELFOXA_CLSID + * MTDLL crashes due to problems with ExecScript(), so use VFP9 version for now +* lcMTDLLProgID = "ParallelFoxMTA.Application" + lcMTDLLProgID = "ParallelFoxMT.Application" +Otherwise && VFP 9.0 + lcVFPProgID = "VisualFoxPro.Application." + SUBSTR(VERSION(4),2,1) + lcParallelFoxProgID = "ParallelFox.Application" + lcRegFreeParallelFox = Evl(This.oParPoolMgr.cRegFreePath, "ParallelFox.exe") + lcRegFreeClassID = PARALLELFOX_CLSID + lcMTDLLProgID = "ParallelFoxMT.Application" +EndCase This.nWorkerNum = lnWorkerNum * Debug mode starts workers in full VFP. If llDebugMode and _VFP.StartMode = 0 - loVFP = CreateObject("VisualFoxPro.Application." + SUBSTR(VERSION(4),2,1)) + loVFP = CreateObject(lcVFPProgID) loVFP.Visible = .t. This.lDebugMode = .t. This.lMTDLL = .f. @@ -2116,7 +2343,23 @@ Else * Must maintain reference to Application object, or COM instance will close even though we also * have reference to Worker object. If !This.lMTDLL - This.oApplication = CreateObject("ParallelFox.Application") + * Prevent worker EXE from stealing focus + DECLARE INTEGER LockSetForegroundWindow IN user32 INTEGER uLockCode + LockSetForegroundWindow(1) && lock + If !This.oParPoolMgr.lRegFreeCOM && This.IsRegistered(lcParallelFoxProgID) or !File(lcRegFreeParallelFox) + * Instantiate registered COM object + This.oApplication = CreateObject(lcParallelFoxProgID) + Else + * Use reg-free COM to launch ParallelFox COM Server + If !File(lcRegFreeParallelFox) + Error 1, lcRegFreeParallelFox + EndIf + Local lcRun + lcRun = [Run /n "] + FullPath(lcRegFreeParallelFox) + [" /automation -Embedding] + &lcRun + This.oApplication = CreateObjectEx(lcRegFreeClassID, GetEnv("COMPUTERNAME")) + EndIf + LockSetForegroundWindow(2) && unlock loVFP = This.oApplication.VFP EndIf lcWorkerVCX = FullPath(This.oParPoolMgr.cWorkerLibrary) @@ -2130,7 +2373,7 @@ Else EndIf * Set up worker events -This.oEvents = NewObject("Events", "ParallelFox.vcx") +This.oEvents = NewObject("Events", This.ClassLibrary) This.oEvents.Name = "WorkerProxyEvents" && to distinguish object from other events objects during debugging BindEvent(This.oEvents, "Complete", This, "Complete", 1) BindEvent(This.oEvents, "ReturnError", This, "HandleError", 1) @@ -2140,9 +2383,10 @@ lcProcedureFile = Evl(lcProcedureFile, "") If This.lMTDLL * Multi-threaded DLL Local loThreadHandler as ThreadHandler of ParallelFox.vcx - loThreadHandler = NewObject("ThreadHandler", "ParallelFox.vcx") + loThreadHandler = NewObject("ThreadHandler", This.ClassLibrary) This.oThreadHandler = loThreadHandler loThreadHandler.oEvents = This.oEvents + loThreadHandler.cWorkerCOMProgID = lcMTDLLProgID loThreadHandler.StartThread(lcDirectory, This.oParPoolMgr.cWorkerClass, lcWorkerVCX, lcWorkerApp, lcProcedureFile) Else lcWorkerVCX = FullPath(This.oParPoolMgr.cWorkerLibrary) @@ -2152,16 +2396,6 @@ Else lcWorkerCmd = Textmerge([NewObject("<>", "<>", "<>")]) This.oWorker = loVFP.Eval(lcWorkerCmd) - * In Windows XP and earlier, main process can lose focus when - * instantiating COM EXE. Make sure we keep it. - * This issue apparently only affects IDE, and can cause wrong window - * to get focus at runtime if workers started when main VFP window is not visible. - * So, only applying to IDE. May revisit if issue presents itself at runtime or other scenarios. - If _VFP.StartMode = 0 and Os(3) < "6" and GetForegroundWindow() <> lhWndForeground - DECLARE INTEGER SetForegroundWindow IN user32 INTEGER hwnd - SetForegroundWindow(lhWndForeground) - EndIf - This.oWorker.SetMainProcess(_VFP, lcWorkerApp) This.oWorker.DoCmd("CD " + lcDirectory) If !Empty(lcProcedureFile) @@ -2170,7 +2404,7 @@ Else EndIf This.oWorker.SetWorkerEvents(This.oEvents) EndIf - + ENDPROC PROCEDURE complete Lparameters lvReturn @@ -2185,7 +2419,9 @@ BindEvent(This.oEvents, "ReturnError", This, "HandleError", 1) This.lComplete = .t. *JAL* This.lBusy = .f. *JAL* This.oParPoolMgr.nBusyWorkers = This.oParPoolMgr.nBusyWorkers - 1 -This.oParPoolMgr.ProcessQueue() +If Vartype(This.oParPoolMgr) = "O" and !IsNull(This.oParPoolMgr) + This.oParPoolMgr.ProcessQueue() +EndIf ENDPROC PROCEDURE handleerror @@ -2195,6 +2431,37 @@ Debugout Time(0), "(" + Transform(This.nWorkerNum) + ")", Program(), lnError, lc This.Complete() ENDPROC +PROCEDURE isregistered +* Check if COM object is registered +Lparameters lcLookUpKey + +* Registry roots +#DEFINE HKEY_CLASSES_ROOT -2147483648 && BITSET(0,31) +#DEFINE HKEY_CURRENT_USER -2147483647 && BITSET(0,31)+1 +#DEFINE HKEY_LOCAL_MACHINE -2147483646 && BITSET(0,31)+2 +#DEFINE HKEY_USERS -2147483645 && BITSET(0,31)+3 + +* Load DLLs +Clear Dlls RegOpenKey +Clear Dlls RegCloseKey +LOCAL nHKey,cSubKey,nResult +DECLARE Integer RegOpenKey IN Win32API ; + Integer nHKey, String @cSubKey, Integer @nResult +DECLARE Integer RegCloseKey IN Win32API ; + Integer nHKey + +* Try to open key +Local lnErrorCode, lnSubKey +lnSubKey = 0 +lnErrorCode = RegOpenKey(HKEY_CLASSES_ROOT,lcLookUpKey,@lnSubKey) +If lnErrorCode = 0 && success + * Close key + =RegCloseKey(lnSubKey) + Return .t. +Else + Return .f. +EndIf +ENDPROC PROCEDURE sendcommand * Send command to worker process. Lparameters loCommand as Command of ParallelFox.vcx @@ -2242,6 +2509,7 @@ Class[END RESERVED1] [START RESERVED3] *complete Fires when worker has finished executing command. *handleerror Fires when error returned from worker. +*isregistered Check if COM object is registered. *sendcommand Send command to worker process. *stopworker Stop worker process. _memberdata XML Metadata for customizable properties From 755271bc2b1a43ca4bb59a589c1413535509ae06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Toni=20K=C3=B6hler?= <44064940+ToniKoehler@users.noreply.github.com> Date: Sat, 8 Nov 2025 22:41:44 +0100 Subject: [PATCH 2/3] CheckWorkerHealth in Parallel.Wait() to prevent deadlock If, for some reason, one or more workers crash and either parallelfox.exe or a thread stops working, the Parallel.Wait() call in the main process will continue waiting for that "busy" worker without realizing that it has crashed. This results in a deadlock. To prevent this, we now check both the Out-of-Process and In-Process workers to ensure they are still alive. --- parallelfox.VCT | Bin 127049 -> 131409 bytes parallelfox.vca | 83 ++++++++++++++++++++++++++++++++++++++++++++++-- parallelfox.vcx | Bin 4520 -> 4520 bytes 3 files changed, 80 insertions(+), 3 deletions(-) diff --git a/parallelfox.VCT b/parallelfox.VCT index ebd19458aa508b91f8eb4aaa135b3eb5fd3106c5..3f4d6020750ec10d13aed3015da47a25543936a6 100644 GIT binary patch delta 5585 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 delta 1546 zcmY*Z4Qx|Y6h7y^{=K$aTiVfXE3|X`Z4E5j1pFZ|9mWi191Bs@@l(rVA$wg(TR|LT zfPsiaU|i%O%g{wdXEX$!@}l9-1du6-8=?tj>Wre%j9JLYHp4p4YnLV7%Z^;`_4+fV>|mn$aInXM-Fgmn?P|`{y`ff-Tf9zwGhkOO^Xmi>xvHha z!PHM4`7%#E`FEe7zP8fJnDBY#s}psv3YP2w%=0pqNP5Ji&R#do$jl-2LaUb<$}1`= zd8fHh3oOQW4T_-}SGnwG(s##$Oka10T}PYC=eG@-NY5k;TIoTMxxM&(2=))pVM0W1yxtfP+) z5E_Wz&_O)YfIg4j0QJhrU;zt_XiKi+F1OZq3+H*2B^)iExmXoWY?Jev7MX?DoNE0; zFEDlB{U1f`EhAnHs(*BM2epQ3tSw-HUzFbY=;hAuef*yin^v+1>uefjy5LrFcB56f zxB$zQ{=HbJ+&hF-UaEq2GSD#DxN0h{I-oahEHst8dgD3^=t1M7X#idHUgg(ruf~oX zJgDqz!YZ)|(-!z*)F>&xHsJ%F+l(=M>zA4V8qKIhn|@heCh zpeS9T8v(4TA{b&YB*3r%Mi5-7+>AB?bzRq@U=``BF;LoMbQ^TL@}Z22rW#?$48xX* zAX_%9QTbQKG8C24C>Cm~qPWzm9ovcradwwOU&i>j4d}jZ+7gR5N%2H~=2+-E8*7&0 z@mPGHvZ4ju`6+icI2RI9G8-F_!->QKWqS)wLyOYhg88jG^CnYD$&Jm4WVksZotrQ^ zAQG3tNhv8sHzmU_%2Ki3a4Nt7>m`}Yla8gntiuEmJ%OB>&^h|#aj9`bT#_QOXhnWD zEPE!q^1+!&H+d*uE>PBNqeJ<_HoUI%Y{#RmJ+z6f6ttXhhj>mQzhWHx7Oyc_q*NKM-D)Bqh0d5j+B>3s96b1o)BQ6sjCoUEN3=oeILPa$7 zcrm~bi59}l834CuQ4pQEH%Pl@100z{cG5>ltHdkmw=kb{JLzYMx0eDeBHc;)4DsV3 kfG0_xAbpnjZNduDncJkF-v14LF(7J&4^Yfg>E0In2TQ`X&;S4c diff --git a/parallelfox.vca b/parallelfox.vca index 988eb54..99b9288 100644 --- a/parallelfox.vca +++ b/parallelfox.vca @@ -604,7 +604,7 @@ Pixels[END RESERVED6] [START PROPERTIES] Name = "parallel" _events = NULL -_memberdata =  1223 +_memberdata =  1289 cpucount = 0 oparpoolmgr = .NULL. [END PROPERTIES] @@ -711,6 +711,31 @@ Debugout Time(0), Program() This.oParPoolMgr.Cancel() ENDPROC +PROCEDURE checkworkerhealth +LOCAL lnWorkerItem, lnWorkersAlive, loWorker + +lnWorkerItem = 0 +lnWorkerCount = THIS.oParPoolMgr.Workers.Count + +FOR lnWorkerItem = 1 TO lnWorkerCount + IF TYPE("THIS.oParPoolMgr.Workers.Item(lnWorkerItem).Name") != "C" + EXIT + ENDIF + loWorker = THIS.oParPoolMgr.Workers(lnWorkerItem) + IF !loWorker.IsWorkerRunning() + IF loWorker.lBusy && If worker is idle, don't reduce nBusyWorkers + THIS.oParPoolMgr.nBusyWorkers = MAX(THIS.oParPoolMgr.nBusyWorkers - 1, 0) + ENDIF + THIS.oParPoolMgr.Workers.REMOVE(lnWorkerItem) + THIS.oParPoolMgr.nWorkerCount = THIS.oParPoolMgr.nWorkerCount - 1 + lnWorkerItem = lnWorkerItem - 1 + ENDIF +NEXT + +lnWorkersAlive = THIS.oParPoolMgr.Workers.COUNT + +RETURN lnWorkersAlive +ENDPROC PROCEDURE clearqueue * Remove all pending commands from queue. @@ -925,6 +950,7 @@ Debugout Time(0), Program(), "Start" Local lnKey lnKey = 0 Do while .t. + This.CheckWorkerHealth() * Sleep() blocks worker processes, so use INKEY() to wait Try lnKey = Inkey(.1, "H") @@ -963,6 +989,7 @@ Class[END RESERVED1] *call Execute/call function on worker. *callmethod Execute/call class method on worker. *cancel Clear remaining queue and set IsCancelled property for all workers. +*checkworkerhealth Check Workers are alive and healthy *clearqueue Remove all pending commands from queue. *createtemptable Create temporary table that can be used by workers with Worker.OpenTempTable(). *deletetemptable Delete temporary table previously created with CreateTempTable(). Make sure Worker.CloseTempTable() is used to close table/DBC in workers. @@ -1404,7 +1431,7 @@ Pixels[END RESERVED6] [OBJNAME] threadhandler [START PROPERTIES] Name = "threadhandler" -_memberdata =  777 +_memberdata =  839 cmtscript = cworkercomprogid = ParallelFoxMT.Application lreleaseprocessed = .F. @@ -1464,6 +1491,40 @@ This.nThreadHandle = CreateThreadWithObject( ; This.nThreadID = lnThreadID +ENDPROC +PROCEDURE isthreadrunning +#DEFINE CON_ThreadQueryInformation 0x0040 +#DEFINE CON_ExitCodeStillActive 259 + +LOCAL llPidOk, llReturn, lnExitCode, lnPid, lnPidMainProcess, lnSuccess, lnThreadHandle + +DECLARE INTEGER OpenThread IN kernel32.DLL ; + INTEGER dwDesiredAccess, INTEGER bInheritHandle, INTEGER dwThreadId +DECLARE INTEGER CloseHandle IN kernel32.DLL INTEGER hObject +DECLARE INTEGER GetExitCodeThread IN kernel32.DLL ; + INTEGER hThread, INTEGER @lpExitCode +DECLARE INTEGER GetProcessIdOfThread IN kernel32.DLL INTEGER hThread + +lnThreadHandle = OpenThread(CON_ThreadQueryInformation, 0, THIS.nThreadID) +IF lnThreadHandle = 0 + RETURN .F. && no valid thread (or no access rights) +ENDIF + +lnExitCode = 0 +lnSuccess = GetExitCodeThread(lnThreadHandle, @lnExitCode) +IF lnSuccess > 0 + llPidOk = .T. + lnPidMainProcess = _VFP.ProcessId && check that threadid is child of processid from mainprocess + IF VARTYPE(lnPidMainProcess) = "N" AND lnPidMainProcess > 0 + lnPid = GetProcessIdOfThread(lnThreadHandle) + llPidOk = (lnPid = lnPidMainProcess) + ENDIF +ENDIF +CloseHandle(lnThreadHandle) + +llReturn = llPidOk AND lnExitCode = CON_ExitCodeStillActive + +RETURN llReturn ENDPROC PROCEDURE release * Release thread and object references @@ -1574,6 +1635,7 @@ Class[END RESERVED1] 1[END RESERVED2] [START RESERVED3] *createthread Create thread for MTDLL. +*isthreadrunning Check that thread is still running *release Release thread and object references. *sendcommand Send command to worker thread. *startthread Start thread for MTDLL worker. @@ -2259,7 +2321,7 @@ Pixels[END RESERVED6] Height = 16 Name = "workerproxy" Width = 99 -_memberdata =  831 +_memberdata =  893 lbusy = .F. lcomplete = .F. ldebugmode = .F. @@ -2462,6 +2524,20 @@ Else Return .f. EndIf ENDPROC +PROCEDURE isworkerrunning +LOCAL llReturn + +DO CASE + CASE !THIS.lDebugMode AND !THIS.lMTDLL + llReturn = TYPE("This.oApplication.VFP") = "O" + CASE !THIS.lDebugMode AND THIS.lMTDLL + llReturn = This.oThreadHandler.IsThreadRunning() + OTHERWISE + llReturn = .T. +ENDCASE + +RETURN llReturn +ENDPROC PROCEDURE sendcommand * Send command to worker process. Lparameters loCommand as Command of ParallelFox.vcx @@ -2510,6 +2586,7 @@ Class[END RESERVED1] *complete Fires when worker has finished executing command. *handleerror Fires when error returned from worker. *isregistered Check if COM object is registered. +*isworkerrunning Check for ParallelFox Application COM object, that it is still running *sendcommand Send command to worker process. *stopworker Stop worker process. _memberdata XML Metadata for customizable properties diff --git a/parallelfox.vcx b/parallelfox.vcx index 2b7af04097a435f10deb73036c6bfae03fe3de45..03db3eabc836e69c4c93e0130fd308d8fb4e9d42 100644 GIT binary patch delta 1177 zcmYLJTSydP6h1TV%&xnOyX$S0F5cJ;F>BYI*%>cXJ4xYX-NnsCEXg3dJk&!4p%PLe zVsue{A)=QsOnr)lAOZu$hwvrH2p_ep5*a}dh#;Mr|F{2#^PkV}ob!3+%$c33n5nRZ zid~M3?SakM6u~;rB!J&Giu+~i$K3!=Z&7UC+n76&1#sWNxgm-8+?qHpkp($Ux}@BK zddx6SC;BFaSIKS3Ul_o@YS3annql7lEVEv4ka;;lR5?(#FF=Fu8UVge@x%sft_ke~ z8IY%9L#*w~$V{@fJM1)jZ#lr7Q@rvD=Jumy@DX>vGXhcL<fay9e5+p))o>=51F{y|g z%C3-8Sg@5#gseJJ;;wYsSbdjdQO9ze#8P+;mjAU5R^ja+% zQKE();2PR_w01P}Y!3qjab!diVCT6&NxW5z_u%{RKKxI?Yp=B;`R44}NYLXm_A?a` zy_ezsW>m!oP9GOw{spfNmI55aoY5-I6_r~3ZeqW2(wyULQ6mvmS2b0cV7st{{w2e& z1_3VO6Q2$WF#LJ0WH;}+9AC#eT40Ck`suI7 JiVgSP_y^Dom6>2{IMRPyH2YP(+17 zT~Vlk6b9>qAN*1fBu6^myQ}QVj0xBEwo-x$b4ZJX|mQ-jnuHiWDCIaO7r6}BzFk4 zom{d$j8AadLm0_&+I==wIb02}c$8sk&tvX2)F*hwEg2>Qwy@YE8mzzAc2)H|*wJqd zVAO4XR!6S)uy}&(vmX#U99Ym!a`xiAQ70?dMBmDBtc1nVUI1T|VQL$QkXI~vog^+- zkcK>;wGRt6T|}@n6OUS}sJy;kHbVOg6!Nc7Y14y^y4DoYbg>-!p;qG!TGc?9@5jXjl&hcYP{BVSisiMvfS z4q!#5S&nl9EH?81tKTt9?T8omi29i4(b$H^==k1)9XrW_qbS~rvpI)4gnEq6X<;0o z9+x{)BfyJAb56~8-j6)rPSIXWa>S{I#T{(Pqxniw4+cV-76|xH;ccj#MgN*(UmD;H zZXjb9z;T+LQ@euGUqemRnz1G0z0J}0)_KP%k&K$3h+;kpE#r1@4_Wi1`92JMU$??mJ_|JfNEG@w0IkQJK4)`j5`&G9YTT=|$ I8hz`D|H+N4uK)l5 From ddee70bf0f05409cc48f106fe93786d8ad813d51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Toni=20K=C3=B6hler?= <44064940+ToniKoehler@users.noreply.github.com> Date: Sat, 8 Nov 2025 22:47:33 +0100 Subject: [PATCH 3/3] New public variable gnParallelWorkernNum for better debugging and error handling If you need to know on which worker you running in DebugMode or you work with you own error handler, now the workernum is returned by parameter lcCode of ReturnError() and you can access public var gnParallelWorkerNum everywere inside your worker. --- parallelfox.VCT | Bin 131409 -> 132451 bytes parallelfox.vca | 17 ++++++++++++++++- parallelfox.vcx | Bin 4520 -> 4520 bytes 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/parallelfox.VCT b/parallelfox.VCT index 3f4d6020750ec10d13aed3015da47a25543936a6..5e6bb4253c5f40ff30e11ce57499b186f4fc1ebc 100644 GIT binary patch delta 2546 zcmah~eQZxJNMaS zFv0eu^X|RpoO{l_=bqm=Q_pJu^i{1E1Wg10$eWgFa)j#SPYw|^s}QVgo#|l~5_(wN zWM?BP8|yY1S*ym#K4Yn3M%8X*d`8ft+$fL^c2n=j{#5m@NSNc=nn3`FG(3-8U zT&iZ)mT~r?eg}(~tgOm>ntfYne#@d0*`#1&vZ^ubtNTRD%C=Zt-LkT6UG@?B2R+l( znOMHT$S#|!vR`kx(rDgNoL-;;!BaccoQ_zm?cy2%*Q=1esKOt97jayQvp!L@vzfZsf= z1-{ySQwwZs z(R7lIgkuT1Kb4F~qoWk3k?^QQlY6O@N+nbDAPoilKDs^N?e6gg>6-R6>sNPltZskU zppQp9$svh8O3RZLYQHOFcG^OHzAnd3cQEusU&QMePV|LS;doq%FGk6U+wFdPD-8wR z{{F5&u=k!?t<>8NH{*3+wn7UsX`^{+Sumrtc$ zwcpPd8x`@44GJq!BX5CHg!)p+rzc9f@+*_1lGx<$PLgIE-kl_SwTCN;E%1XvqwGG1 z%tkqWj##CVL37Dp6VK*GJVQ0E8%zF6=a?^Y6=XTMiXK%S;`f090aTb-0)=LPf&s~h z1c0iuP9=ToZ)q0fznvpZBrjXelNF>zZa+^_fmVJuD43LcuSV5cZt!Dd85NVQFzZ6@ zgG$&`;OV63OXSW_(t;8*M%aU{PVfN#MZR&KY;~4JJcNq5(&5XS+Hez>Ae7p3;a`zg ziB^PuZwNC^F8-SQi1^s{TMxCJCBLks+WJ~5^R5#r`D4?W`57g{^6|5Iln@lsmg6;L~YD;OY!}q z9FDOe%#7IH37U>dbX+MwbUZ30=-6m1F-()=i83;y;lxl}O0`nSHSD7C9JJ8%aLq%xm?csqB&sD`?{zt6pf9#;tJFZdR;uZv#ZZlil@VVk2L4F z?e_J%692)oLGSf<`nqzPPLR_Y+3-Bk%cnMw4*AMqQZN7WDEVym`RjFu<=6q@l)pJa zF63SrCqlPp69bT^Sg-Y)=U0zur5|twRNFh7-YnY($2xQX*On9iCOfNrdsucL8_b8Ra?FRs6j>vo)rZ2-IE z!3?nrZI;~TOXOTLKYcy>VV6N(M}+0<3%}LN_+Z+XEW*EX`gzPa6$VNs3#2;t^CJ4m z6;BdVjm7%@V#PUQ1vq2n73ZmT%p0ZR`~)iv|0c0~GaSE@eL*rlAaoFRe8QHgQE)(R zcN+x5I24yd=qd+?c}^Mf0&JwB2NARzI}DbO{}R#x(Ss}j#n`e2QHKKsTaD8(=b2Ff zwzzBWspbi?R&k3O_>>R}y1EMNgNgMSMdRutvtk)yB5Xa@aHq1ygvvHjT!~c#l`A;- z^Lj{+q&&%y5zNi96D)4rvktb*d9Iz9=UCN31$f+jAz#3M&)%nlK5a1I@%H!oIg%h?a#d1FP!pl_!A*z6+V{pnaT zLC0h1=pCJJEHOl}vhs%K8=`x$%k7cUX(>gAq&;K9!yIP!1&V@`Qb)UQL(5=C!>riN z(c>u};Zb_m61&(&$~)Kp?t8lbZ-x%sZ3f*e>vJt$PidHgcpL?m)S%D4_~A0)-^%$g A6#xJL delta 1457 zcmYjRdu&r>6hG&?ZP(Uyw{=_FxmWjqZQa-cEQle-m8xtyaj-grATpq23L8?U8&kHa ziJ&3EJmAF)CH!GX6v*b)UE&fV;)0J57%zb#O87&90byxY7uw>v?L@!idz{zrJLlZr zcdxx~9(~(v27!wJ00$l8*ypj3By#94YXvnJEcCpnr}K6%SzHAyyQo#Nkt{8zenT3a z5$OVFQim&-YFugRuyn>m2jm9IGv=@@gAPeHHT!`q0}YByx+E3R5)K*^8MuV{%IDCC z)l0P=nf4fLlx{yp?d93DL$Xqbuzl&UnAqmk;a?RQ>O$9Tv%1Ceo0Y2N4t0C^@5O5M znk<8QIA9g3qU*2_&tw6F2?89XP?8Jedn1}mZ$No$>fDctqWi~?_RC` zdrZp^zkssr0u~gf!-BHXs6UJl5pd(26vKErOp9LiM26X3dX@&vloD-E9^U0R%LPvvHqVSZ`E26`Q7O8f{` z(VWIQ+S%AI4hWhVsGyeuh52t86Dfd`!S?WZsR^K1N7f4%mT0iot&ZPvyrR8ipgSvIu2Pg_;)qLWcR9bOsn=v^K5Wz`522jtaF*Wx1TwcmcIs@Mx7WZTvvx1sBeW%jnH#p4lPdN$|`BPrM#%Yu~7A3g8BFtTm73}APkE8yZtV#n$50}Hl3>xinX7a(yW;;JnKf~^`@`Kw?(XIt$ zQx^SQCW4bmO$AqSjBa6qRw+jYutMGa#s(zE=C5dOcTd)R%KVw4_x@i-EjQDt?roMu zLf|O)pqiHUyhi7H=F)~HCsp>YC_Kjhn0Z`e8wb_$Y~r(_0H8xh(U~ICKyQ|)dQf>Z zj1O!1y|~k>84utmX!6*s7K=ss`5@+LBL}e>mCNrW8KYRgz`M+$Y#Mmdri^`#x&Htv CP1KP9 diff --git a/parallelfox.vca b/parallelfox.vca index 99b9288..c9b1049 100644 --- a/parallelfox.vca +++ b/parallelfox.vca @@ -281,6 +281,9 @@ For lnRow = lnErrorRow to 1 step -1 EndFor +* Get Workernum back to main process in case of error | TONI KOEHLER 2025-11-08 +lcCode = lcCode + "<>" + IIF(VARTYPE(gnParallelWorkerNum) = "N", TRANSFORM(gnParallelWorkerNum), "MTDLL") + "<>" + Chr(13) + Worker.ReturnError(lnError, lcMethod, lnLine, lcMessage, lcCode) * Exit current code containing error and return to command processor @@ -723,7 +726,7 @@ FOR lnWorkerItem = 1 TO lnWorkerCount ENDIF loWorker = THIS.oParPoolMgr.Workers(lnWorkerItem) IF !loWorker.IsWorkerRunning() - IF loWorker.lBusy && If worker is idle, don't reduce nBusyWorkers + IF loWorker.lBusy && If worker is idle, don't reduce nBusyWorkers THIS.oParPoolMgr.nBusyWorkers = MAX(THIS.oParPoolMgr.nBusyWorkers - 1, 0) ENDIF THIS.oParPoolMgr.Workers.REMOVE(lnWorkerItem) @@ -2036,6 +2039,13 @@ EndIf ENDPROC PROCEDURE Init _VFP.Caption = "ParallelFox Worker" + +* Set caption with Workernum und ProcessId for better debugging | TONI KOEHLER 2025-11-08 +IF VARTYPE(gnParallelWorkerNum) = "N" + _VFP.Caption = _VFP.Caption + ": " + TRANSFORM(gnParallelWorkerNum) + _VFP.Caption = _VFP.Caption + " [" + TRANSFORM(_VFP.ProcessId) + "]" +ENDIF + * Make sure ParallelFox.vcx can be found in workers Set Path To (JustPath(This.ClassLibrary)) Additive * Set Unattended mode unless in debug mode @@ -2434,6 +2444,11 @@ Else lcWorkerApp = "" EndIf +* Helps to identify the worker when using own error handler, e.g. loParallel.BINDEVENT("ReturnError"...)| TONI KOEHLER 2025-11-08 +IF !This.lMTDLL + loVFP.SETVAR("gnParallelWorkerNum", THIS.nWorkerNum) +ENDIF + * Set up worker events This.oEvents = NewObject("Events", This.ClassLibrary) This.oEvents.Name = "WorkerProxyEvents" && to distinguish object from other events objects during debugging diff --git a/parallelfox.vcx b/parallelfox.vcx index 03db3eabc836e69c4c93e0130fd308d8fb4e9d42..04a3ecd41ecf2a3ed77178d4110e1c73d793f9b0 100644 GIT binary patch delta 832 zcmYLHOK1~e5dQvc9wtrdrfOA0vnC``VrrUnw=vDZL^LAW1{6Jr)p{tgMJ#ym{ip}E zMFcyCB8YgYRu7^%S+s@P9z6KC2^PVCMSD;Wf`=%Gv-y|oz&G>lH~(YypP39#hM$%l zfw?QF+g}A3r8&p6SfpX+$ELxEOufqju18dIJr~z#qkCOsAOw{6*Y{zFH(q2sbK2T5 z>T{=+qC&H-y|m=cJBb(EqAklN;$5m-R^?c*cy^`KhBe?}Msm})k?^cgG?jABHPq$_ zyAJV&YBPz33kT98XxhrB7MfMra zA9Vn!vATp=Qhj&wIZY~uIrdom+-}yGPj45fl$l!=(||N%#H!zv*K4)9e#yEPRR31p zFxn$l6ZUXSS$tPkKw)0FEa2(2YYTB*-xxPZ3*4Y>OI^=(2GzBu01RQ-Im;IDy6aP5SB$6Lqyc`6VNsoCH?h{eBet^+;Kqrn4=#{#fH38f z+G#xFPO)P+rR8wNopp^+ei-*|7;7|iUR9xZNbvM#t{D-4@iy5dj?A-33F(+|g|NvJ zReQ)#*@(>1hGAcVIq|utxV;<(=-NV`e- zUMGNlrz*h=>b}wJZsAlY>Vgk+P$&ADd^>}=C(;p*0PLZf;RdONBI|Rl+H-YcN1(px z*&x7qn*L;*LFu=wTyg-c5^4)lTX5KJ{17M{O|>>#ed3Zw02Sc z6k$lz*_n*CK1`!#U(#LOPfc5r-@V+V(!Kl<{Ng<_5??McaKDiEG*0t9QmIr