4D v16.3

Preemptive 4D processes

Home

 
4D v16.3
Preemptive 4D processes

Preemptive 4D processes  


 

 

4D Developer Edition 64-bit for OS X (and for Windows, available starting with 4D v16 R2) offers a powerful feature allowing you to execute 4D code in preemptive processes. Thanks to this new feature, your 4D compiled applications will be able to take full advantage of multi-core computers so that their execution will be faster and they can support more connected users.

When run in preemptive mode, a process is dedicated to a CPU. Process management is then delegated to the system, which can allocate each CPU separately on a multi-core machine.

When run in cooperative mode (the only mode available in 4D until 4D v15 R5), all processes are managed by the parent application thread and share the same CPU, even on a multi-core machine. 

As a result, in preemptive mode, overall performance of the application is improved, especially on multi-core machines, since multiple processes (threads) can truly run simultaneously. However, actual gains depend on the operations being executed.

In return, since each thread is independent from the others in preemptive mode, and not managed directly by the application, there are specific constraints applied to methods that you want to be compliant with preemptive use. Additionally, preemptive execution is only available in certain specific contexts.

The use of preemptive mode is available in 4D 64-bit versions only. The following execution contexts are currently supported:

Preemptive execution
4D ServerX
4D remote-
4D single-userX
Compiled modeX
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:

  • 4D commands: thread safety is an internal property. In the Language Reference, thread-safe commands are identified by the icon. A large part of 4D commands can run in preemptive mode.
  • Project methods: conditions for being thread-safe are listed in the Writing a thread-safe method paragraph.

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:

  • Can be run in preemptive processes: By checking this option, you declare that the method is capable of being run in a preemptive process and therefore should be run in preemptive mode whenever possible. The "preemptive" property of the method is set to "capable".
    When this option is checked, the 4D compiler will verify that the method is actually capable and will return errors if this is not the case -- for example, if it directly or indirectly calls commands or methods that cannot be run in preemptive mode (the entire call chain is parsed but errors are only reported to the first sublevel). You can then edit the method so that it becomes thread-safe, or select another option.
    If the method's preemptive capability is approved, it is tagged "thread-safe" internally and will be executed in preemptive mode whenever the required conditions are met. This property defines its eligibility for preemptive mode but does not guarantee that the method will actually be run in preemptive mode, since this execution mode requires a specific context (see When is a process started preemptively?).
  • Cannot be run in preemptive processes: By checking this option, you declare that the method must never be run in preemptive mode, and therefore must always be run in cooperative mode, as in previous 4D versions. The "preemptive" property of the method is set to "incapable".
    When this option is checked, the 4D compiler will not verify the ability of the method to run preemptively; it is automatically tagged "thread-unsafe" internally (even if it is theoretically capable). When called at runtime, this method will "contaminate" any other methods in the same thread, thus forcing this thread to be executed in cooperative mode, even if the other methods are thread-safe.
  • Indifferent (default): By checking this option, you declare that you do not want to handle the preemptive property for the method. The "preemptive" property of the method is set to "indifferent".
    When this option is checked, the 4D compiler will evaluate the preemptive capability of the method and will tag it internally as "thread-safe" or "thread-unsafe". No error related to preemptive execution is returned. If the method is evaluated as thread-safe, at runtime it will not prevent preemptive thread execution when called in a preemptive context. Conversely, if the method is evaluated "thread-unsafe", at runtime it will prevent any preemptive thread execution when called.
    Note that with this option, whatever the internal thread safety evaluation, the method will always be executed in cooperative mode when called directly by 4D as the first parent method (for example through the New process command). If tagged "thread-safe" internally, it is only taken into account when called from other methods inside a call chain.
 

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:

OptionPreemptive property value (interpreted)Compiler actionInternal tag (compiled)Execution mode if call chain is thread-safe
Can be run in preemptive processescapableCheck capability and return errors if incapablethread-safePreemptive
Cannot be run in preemptive processesincapableNo evaluationthread-unsafeCooperative
IndifferentindifferentEvaluation but no errors returnedthread-safe or thread-unsafeIf 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:

  • If the process method is thread-safe (validated during compilation), the process is executed in a preemptive thread.
  • If the process method is thread-unsafe, the process is run in a cooperative thread.
  • If the preemptive property of the process method was set to "indifferent", by compatibility the process is run in a cooperative thread (even if the method is actually capable of preemptive use). Note however that this compatibility feature is only applied when the method is used as a process method: a method declared "indifferent" but internally tagged "thread-safe" by the compiler can be called preemptively by another method (see below).

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 chainCompilationResulting thread safetyExecutionComment
OKPreemptiveCallComp is the parent method, declared "capable" of preemptive use; since MyComp is thread-safe internally, CallComp is thread-safe and the process is preemptive
ErrorExecution is impossibleCallDial 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
OKCooperativeSince CallDial is declared "incapable" of preemptive use, compilation is thread-unsafe internally; thus execution will always be cooperative, regardless of the status of MyDialog
OKCooperativeSince CallComp is the parent method with property "Indifferent", then the process is cooperative even if the entire chain is thread-safe.
OKCooperativeSince 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:

  • The PROCESS PROPERTIES command allows you to find out whether a process is run in preemptive or cooperative mode.
  • Both the Runtime Explorer and the 4D Server administration window display specific icons for preemptive processes (as well as worker processes):
    Process typeIcon
    Preemptive stored procedure
    Preemptive worker process
    Cooperative worker process

To be thread-safe, a method must respect the following rules:

  • It must have either the "Can be run in preemptive processes" or "Indifferent" property
  • It must not call a 4D command that is thread-unsafe.
  • It must not call another project method that is thread-unsafe
  • It must not call a plug-in
  • It must not use Begin SQL/End SQL code blocks
  • It must not use any interprocess variables(*)
  • It must not call interface objects(**) (there are exceptions however, see below).

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:

  • Standard error dialog. The dialog is displayed in the user mode process (on 4D single-user) or the server user interface process (4D Server). The Trace button is disabled.
  • Standard progress indicators
  • ALERT, Request and CONFIRM dialogs. The dialog is displayed in the user mode process (on 4D single-user) or the server user interface process (4D Server).
    Note that if 4D Server has been launched as a service on Windows with no user interaction allowed, the dialogs will not be displayed.

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:

  • SAVE RECORD
  • SAVE RELATED ONE
  • DELETE RECORD
  • DELETE SELECTION
  • ARRAY TO SELECTION
  • JSON TO SELECTION
  • APPLY TO SELECTION
  • IMPORT DATA
  • IMPORT DIF
  • IMPORT ODBC
  • IMPORT SYLK
  • IMPORT TEXT

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:

  • When called from a preemptive process, a DocRef reference is only usable from that preemptive process.
  • When called from a cooperative process, a DocRef reference is usable from any other cooperative process.

For more information about the DocRef reference, please refer to the DocRef: Document reference number section.



See also 

Command name
Using preemptive Web processes

 
PROPERTIES 

Product: 4D
Theme: Processes

 
HISTORY 

Created: 4D v15 R5

 
ARTICLE USAGE

4D Language Reference ( 4D v16.1)
4D Language Reference ( 4D v16.2)
4D Language Reference ( 4D v16.3)