4D v16.3Preemptive 4D processes |
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
4D v16.3
Preemptive 4D processes
|
Preemptive execution | |
4D Server | X |
4D remote | - |
4D single-user | X |
Compiled mode | X |
Interpreted mode | - |
If the execution context supports preemptive mode and if the method is "thread-safe", a new 4D process launched using the New process or CALL WORKER commands, or the "Run method" menu item, will be executed in a preemptive thread.
Otherwise, if you call New process or CALL WORKER from an execution context that is not supported (for example on a remote 4D machine), the process is always cooperative.
Note: You can run a process in preemptive mode from a 4D remote by starting a stored procedure on the server with the language, for example using Execute on server.
4D code can only be run in a preemptive thread when certain specific conditions are met. Each part of the code being executed (commands, methods, variables, etc.) must be compliant with preemptive use. Elements that can be run in preemptive threads are called thread-safe and those that cannot be run in preemptive threads are called thread-unsafe.
Note: Since a thread is handled independently starting from the parent process method, the entire call chain must not include any thread-unsafe code; otherwise, preemptive execution will not be possible. This point is discussed in the When is a process started preemptively? paragraph.
The "thread safety" property of each element depends on the element itself:
Basically, code to be run in preemptive threads cannot call parts with external interactions, such as plug-in code or interprocess variables. Accessing data, however, is allowed since the 4D data server supports preemptive execution.
By default, 4D executes all the project methods of your application in cooperative mode. If you want to benefit from the preemptive mode feature, the first step consists of explicitly declaring all methods that you want to be started in preemptive mode whenever possible -- that is, methods that you consider capable of being run in a preemptive process. The compiler will check that these methods are actually thread-safe (see Writing a thread-safe method for more information). You can also disallow preemptive mode for some methods, if necessary.
Keep in mind that declaring a method "capable" of preemptive use makes it eligible for preemptive execution but does not guarantee that it will actually be executed in preemptive mode at runtime. Starting a process in preemptive mode results from an evaluation performed by 4D regarding the properties of all the methods in the call chain of the process (for more information, see When is a process started preemptively? below).
To declare your method eligible for use in preemptive mode, you need to use the Execution mode declaration option in the Method Properties dialog box:
The following options are provided:
Note: A component method declared as "Shared with components and host databases" must also be declared "capable" in order to be run in a preemptive thread by the host database.
When exporting the method code using, for example, METHOD GET CODE, the "preemptive" property is exported in the "%attributes" comment with a value of "capable" or "incapable" (the property is not available if the option is "Indifferent"). The METHOD GET ATTRIBUTES and METHOD SET ATTRIBUTES commands also get or set the "preemptive" attribute with a value of "indifferent", "capable", or "incapable".
The following table summarizes the effects of the preemptive mode declaration options:
Option | Preemptive property value (interpreted) | Compiler action | Internal tag (compiled) | Execution mode if call chain is thread-safe |
Can be run in preemptive processes | capable | Check capability and return errors if incapable | thread-safe | Preemptive |
Cannot be run in preemptive processes | incapable | No evaluation | thread-unsafe | Cooperative |
Indifferent | indifferent | Evaluation but no errors returned | thread-safe or thread-unsafe | If thread-safe: preemptive; if thread-unsafe: cooperative; if called directly: cooperative |
Reminder: Preemptive execution is only available in compiled mode.
In compiled mode, when starting a process created by either New process or CALL WORKER methods, 4D reads the preemptive property of the process method (also named parent method) and executes the process in preemptive or cooperative mode, depending on this property:
The actual thread-safe property depends on the call chain. If a method with the property declared as "capable" calls a thread-unsafe method at either of its sublevels, a compilation error will be returned: if a single method in the entire call chain is thread-unsafe, it will "contaminate" all other methods and preemptive execution will be rejected by the compiler. A preemptive thread can be created only when the entire chain is thread-safe and the process method has been declared "Can be run in preemptive process".
On the other hand, the same thread-safe method may be executed in a preemptive thread when it is in one call chain, and in a cooperative thread when it is in another call chain.
For example, consider the following project methods:
//MyDialog project method
//contains interface calls: will be internally thread unsafe
$win:=Open form window("tools";Palette form window)
DIALOG("tools")
//MyComp project method
//contains simple computing: will be internally thread safe
C_LONGINT($0;$1)
$0:=$1*2
//CallDial project method
C_TEXT($vName)
MyDialog
//CallComp project method
C_LONGINT($vAge)
MyCom($vAge)
Executing a method in preemptive mode will depend on its "execution" property and the call chain. The following table illustrates these various situations:
Declaration and call chain | Compilation | Resulting thread safety | Execution | Comment |
![]() | OK | ![]() | Preemptive | CallComp is the parent method, declared "capable" of preemptive use; since MyComp is thread-safe internally, CallComp is thread-safe and the process is preemptive |
![]() | Error | ![]() | Execution is impossible | CallDial is the parent method, declared "capable"; MyDialog is "indifferent". However, since MyDialog is thread-unsafe internally, it contaminates the call chain. The compilation fails because of a conflict between the declaration of CallDial and its actual capability. The solution is either to modify MyDialog so that it becomes thread-safe so that execution is preemptive, or to change the declaration of CallDial 's property in order to run as cooperative |
![]() | OK | ![]() | Cooperative | Since CallDial is declared "incapable" of preemptive use, compilation is thread-unsafe internally; thus execution will always be cooperative, regardless of the status of MyDialog |
![]() | OK | ![]() | Cooperative | Since CallComp is the parent method with property "Indifferent", then the process is cooperative even if the entire chain is thread-safe. |
![]() | OK | ![]() | Cooperative | Since CallDial is the parent method (property was "Indifferent"), then the process is cooperative and compilation is successful |
4D allows you to identify the execution mode of processes in compiled mode:
Process type | Icon |
Preemptive stored procedure | ![]() |
Preemptive worker process | ![]() |
Cooperative worker process | ![]() |
To be thread-safe, a method must respect the following rules:
Note: In the case of a "Shared by components and host databases" method, the "Can be run in preemptive processes" property must be selected.
(*) Worker processes allow you to exchange data between any processes, including preemptive processes. For more information, please refer to the About workers.
(**) The CALL FORM command provides an elegant solution to call interface objects from a preemptive process.
Methods with the "Can be run in preemptive processes" property will be checked by 4D during compilation. A compilation error is issued whenever the compiler finds something that prevents it from being thread-safe:
The symbol file, if enabled, also contains the thread safety status for each method:
Since they are "external" accesses, calls to user interface objects such as forms, as well as to the Debugger, are not allowed in preemptive threads.
The only possible accesses to the user interface from a preemptive thread are:
A significant number of 4D commands are thread-safe. In the documentation, the icon in the command property area indicates that the command is thread-safe. You can get a list of thread-safe commands in the Language Reference manual.
You can also use Command name which can return the thread safety property for each command.
When a method uses a command that can call a trigger, the 4D compiler evaluates the thread safety of the trigger in order to check the thread safety of the method:
SAVE RECORD([Table_1]) //trigger on Table_1, if it exists, must be thread-safe
Here is the list of commands that are checked at compilation time for trigger thread safety:
If the table is passed dynamically, the compiler may sometimes not be able to find out which trigger it needs to evaluate. Here are some examples of such situations:
DEFAULT TABLE([Table_1])
SAVE RECORD
SAVE RECORD($ptrOnTable->)
SAVE RECORD(Table(myMethodThatReturnsATableNumber())->)
In this case, all triggers are evaluated. If a thread-unsafe command is detected in at least one trigger, the whole group is rejected and the method is declared thread-unsafe.
Error-catching methods installed by the ON ERR CALL command must be thread-safe if they are likely to be called from a preemptive process. In order to handle this case, the compiler now checks the thread safety property of error-catching project methods passed to the ON ERR CALL command during compilation and returns appropriate errors if they do not comply with preemptive execution.
Note that this checking is only possible when the method name is passed as a constant, and is not computed, as shown below:
ON ERR CALL("myErrMethod1") //will be checked by the compiler
ON ERR CALL("myErrMethod"+String($vNum)) //will not be checked by the compiler
In addition, starting with 4D v15 R5, if an error-catching project method cannot be called at runtime (following a thread safety issue, or for any reason like "method not found"), the new error -10532 "Cannot call error handling project method 'methodName'" is generated.
A process can dereference a pointer to access the value of another process variable only if both processes are cooperative; otherwise, 4D will throw an error. In a preemptive process, if some 4D code tries to dereference a pointer to an interprocess variable, 4D will throw an error.
Example with the following methods:
Method1:
myVar:=42
$pid:=New process("Method2";0;"process name";->myVar)
Method2:
$value:=$1->
If either the process running Method1 or the process running Method2 is preemptive, then the expression "$value:=$1->" will throw an execution error.
The use of DocRef type parameters (opened document reference, used or returned by Open document, Create document, Append document, CLOSE DOCUMENT, RECEIVE PACKET, SEND PACKET) is limited to the following contexts:
For more information about the DocRef reference, please refer to the DocRef: Document reference number section.
Product: 4D
Theme: Processes
Created: 4D v15 R5
4D Language Reference ( 4D v16.1)
4D Language Reference ( 4D v16.2)
4D Language Reference ( 4D v16.3)