User interruption of module execution
Users can interrupt module execution by pressing Ctrl+Break only when the module calls the global context method UserInterruptProcessing(). This procedure checks whether the user pressed Ctrl+Break and if they did, it interrupts module execution.
Calling this procedure makes sense in loops if their execution might take significant time.
The interruption is performed unconditionally, without a confirmation request. This allows using the procedure in time-consuming transactions.
Module interruption cannot be used in some handlers. It can only be used in handlers of actions that users explicitly perform in forms or in the global command interface. For example, you can use interruption in a command handler, but you cannot use it in the OnOpen() form handler.
Note that the interruption is only performed when the UserInterruptProcessing() is called. Execution of time-consuming operations that are called from modules, such as queries, cannot be inrerrupted.
Handling script errors
If an application is running in 1C:Enterprise mode and a script error occurs, an error message is displayed. Then the system behavior varies depending on the error type.
For compilation errors, the application is stopped because the system cannot process an object whose module has not been compiled.
For runtime errors, the current script call (up to the top level) is stopped but the application execution is continued.
But if a runtime error occurs during module body execution (i.e. during module initialization), the application is stopped because the system cannot process an object whose module has not been initialized.
You can perform recursive procedure calls in 1C:Enterprise modules. The simplest recursion example is calling a procedure from itself. You can use recursive procedure calls for implementing various algorithms. For example, you can use this for iterating a query result by catalog hierarchy because the number of hierarchy levels is not known in advance.
When implementing a recursive call, you must provide a way to break out of recursion by some condition in order to avoid infinite recursion.
Note that each procedure call level uses a certain amount of memory. During infinite recursion, first the system stops responding due to multiple procedure calls, and then the memory overflow leads to application termination. The system cannot identify this as a module execution error because it cannot determine whether it is intentional behavior or an algoritm error. Therefore, in case of infinite recursion no script error messages are displayed but the application is terminated.
Also note that infinite recursion can be caused not only by explicit procedure calls from itself but also by calling a handler that calls itself. For example, if the OnCurrentPageChange() handler includes a page change, this calls the OnCurrentPageChange() handler recursively. This leads to infinite recursion and application termination.
Storing values in object and form module variables
The 1C:Enterprise platform implements the object lifetime management strategy based on reference counting. Each platform object has a reference counter. If a new reference to an object is created (the object is assigned to a variable), the counter is incremented by 1. If a reference is deleted, the counter is decremented by 1. Once the reference counter value becomes 0, the object is deleted and the memory allocated for the object is released.
This strategy allows creation of circular references. A circular reference appears when objects reference each other. This leads to a situation when each of the objects that form a circular reference cannot be deleted. This, in turn, leads to memory leaks.
Avoid creation of curcular references whenever possible. Following simple rules during configuration development helps you avoid problems related to circular references. If a circular reference is created, define a moment for breaking it to ensure deletion of the objects in a regular way.
It is impossible to name all sutuations that lead to circular references. Here are some examples:
- Storing a reference to an object (or form, or record set) in an object (form, record set) variable leads to a circular reference; the object (form, record set) will never be deleted.
- Storing a reference to a parent object in a child object together with storing a reference to a child object in a parent object leads to a situation when both objects will never be deleted.
Note that, in addition to references created by module variables, references to objects can be created when objects are passed to script methods. For example, adding a value list to itself (as one of the values) leads to a circular reference.
Nontransactional data reading
Note. This article applies to the following DBMS:
When reading data outside a transaction, take into account implementation specifics of nontransactional data reading in file and client/server modes.
In client/server mode, nontransactional reading might return incomplete data.
This is because read operations performed outside transactions use the "read uncommitted" isolation level, so that uncommitted changes made by another transactions are read. The "read uncommitted" isolation level is used because predictable performance of read operations is required and therefore pausing on locks created by other users' transactions is not not acceptable.
A notable example of this behavior is writing a great number of new documents within a transaction and then rolling back the transaction. If you open the list of documents in another 1C:Enterprise session during the transaction, you will see that documents are created in real time. Once the transaction is rolled back, all of the created documents are deleted. This is because dynamic list data is read in nontransactional mode, and therefore uncommitted changes performed within the transaction are visible to other 1C:Enterprise applications.
In the file mode nontransactional reading is performed differently: 1C:Enterprise 8 supports versioning during nontransactional reading. Therefore queries do not select data from uncommited changes of other transactions.
The following figure shows the differences between file and client/server mode for the example described earlier.
Once the transaction is committed, the results will look like this in file and client/server mode:
Despite the fact that versioning is supported in file mode, executing procedures with multiple database queries still leaves a chance to get mismatched data. This can occur if another transaction changes data between the first and the second queries:
"For each" iterator usage specifics
1C:Enterprise features a universal method for iterating collections: the "For each" iterator. You can use it for iterating most of the collections. The iterator allows recording all collection elements to a specified variable, one by one.
For some collections, you can get collection elements by their numeric indexes. You can iterate such collections both by using iterators and by using indexes. An example of such collection is Array.
However, for some collections you cannot get collection elements by indexes because the elements are not ordered. In such cases the "For each" iterator helps iterate the collection. Note that the iteration order should not be interpreted as the order of collection elements. When you use the "For each" iterator on an unordered collection, the resulting iteration order is generally unpredictable. Therefore, building your collection processing algorithms based on iteration order is not allowed. An example of such collection is Map.
It is important to remember that the iteration order might change unpredictably after adding or removing collection elements. This behavior can vary between collections. Therefore, it is recommended that you do not add or remove collection elements while "For each" iterating is being performed. If you need to add or remove elements, we recommend that you use indexes, or mark elements to delete them later, after the iterating is complete.
Passing parameters to procedures and functions by reference and by value
1C:Enterprise script supports two methods of passing parameters to procedures and functions: by reference and by value.
Passing parameters by reference
By default, 1C:Enterprise script passes parameters to procedures and functions by reference. In other words, changing a formal parameter within a procedure or function affects the actual parameter value that is passed to the procedure or function.
Procedure Procedure1(FormalParameter1) FormalParameter1 = 2 * 3; EndProcedure ActualParameter = 10; Procedure1(ActualParameter); // Value "6" will be displayed. Changing a formal // parameter within a procedure affects the // actual parameter value that is passed to the // procedure. The parameter is passed using the // default method: by reference. Message(ActualParameter);
Passing parameters by value
When a parameter is passed to a procedure or function by value, changing the formal parameter value within a procedure does not affect the actual parameter that is passed to the procedure or function. The Val keyword specifies that a parameter is passed by value.
Procedure Procedure2(Val FormalParameter1) FormalParameter1 = 2 * 3; EndProcedure ActualParameter = 10; Procedure2(ActualParameter); // Value "10" will be displayed. Changing a formal // parameter within a procedure does NOT affect the // actual parameter value that is passed to the // procedure. The parameter is passed by value. Message(ActualParameter);
Specifics of passing variales of specific types by value
Passing variables by value has its specifics based on the variable type. Calling context methods or properties of a formal parameter might change the state of the actual parameter passed to the procedure or function.
A value table is passed to a procedure by value. A new instance of the value table is created within the ProcessTable() procedure, and then three rows are added to the instance. This does not affect the value table VT that is passed as an actual parameter to the ProcessTable() procedure.
// The ValTable parameter is passed by value Procedure ProcessTable(Val ValTable) // Creating a value table ValTable = New ValueTable; // Adding two columns to the value table ValTable.Columns.Add("Column1"); ValTable.Columns.Add("Column2"); // Adding rows to the value table For n = 1 To 3 Do NewRow = ValTable.Add(); NewRow.Column1 = "Column"; NewRow.Column2 = n; EndDo; EndProcedure // Creating a value table VT = New ValueTable; // Adding three columns to the table VT.Columns.Add("Column1"); VT.Columns.Add("Column2"); VT.Columns.Add("Column3"); // Adding rows to the value table For n = 1 To 10 Do NewRow = VT.Add(); NewRow.Column1 = "Column1"; NewRow.Column2 = "Column2"; NewRow.Column3 = n; EndDo; // Value "10" will be displayed Message("Number of elements in VT before processing: " + VT.Count()); // Calling the procedure with passing actual parameter VT by value ProcessTable(VT); // Value "10" will be displayed: new value table is saved // to a formal parameter inside the ProcessTable procedure Message("Number of elements in VT after processing: " + VT.Count());
A value table is passed to a procedure by value. The passed value table is grouped within the GroupTable() procedure.
// The ValTable parameter is passed by value Procedure GroupTable(Val ValTable) // Grouping the value table by Column1 and Column3 ValTable.GroupBy("Column1", "Column3"); EndProcedure // Creating a value table VT = New ValueTable; // Adding three columns to the table VT.Columns.Add("Column1"); VT.Columns.Add("Column2"); VT.Columns.Add("Column3"); // Adding rows to the value table For n = 1 To 10 Do NewRow = VT.Add(); NewRow.Column1 = "Column1"; NewRow.Column2 = "Column2"; NewRow.Column3 = n; EndDo; // Value "10" will be displayed Message("Number of elements in VT before grouping: " + VT.Count()); // Calling the procedure with passing actual parameter VT by value GroupTable(VT); // The value table is grouped; value "1" will be displayed Message("Number of elements in VT after grouping: " + VT.Count());
The following figure illustrates what happens when the GroupTable(VT) procedure is called:
(1) Calling the GroupTable procedure. The actual procedure parameter is the VT variable that stores a reference to a value table instance.
(2) Calling the GroupBy method of the value table within the procedure.
(3) The formal parameter references the same value table instance as the actual parameter (the VT variable); grouping the value table instance that is referenced by the VT variable.
(4) The procedure is executed; the value table instance that is referenced by the VT variable is grouped.
Therefore, passing an actual parameter by value in this example does not lead to creation of a value table copy. Calling methods or properties of the value table actually calls methods or properties of the value table that was passed to the GroupTable procedure.
For all types, the difference between passing parameters by reference and by value is in assigning a new value to the actual procedure or function parameter. Calling methods and properties of an actual parameter context (if it has any) affects the formal parameter regardless of the passing method.
Recursion in the script
1C:Enterprise script allows recursive procedure calls (calling a procedure from itself or more complex scenarios where a single procedure might be added to the call stack multiple times).
You can use recursive algorithms for various purposes, such as iterating hierarchical catalogs or processing subaccounts of a chart of accounts.
Note that each procedure call uses up a certain amount of stack memory reserved for performing this call. A proper exit from recursion is the responsibility of a configuration developer.
First of all, avoid scenarios with infinite recursion. The system behavior in a scenario with infinite recursion is described in Infinite recursion.
Also, avoid using algorithms where the number of recursion levels might reach hundreds. The script does not have built-in restrictions to the number of recursion levels but there is a technological restriction. Once the available resources are used up, the application is terminated. The recommended limit of recursion levels is several dozens.
Using temporary files in configurations
We recommend that you use the global context method GetTempFileName() for generating temporary file names. This method has the following advantages:
- The file name is unique.
- If the file is not deleted using 1C:Enterprise script tools, the platform deletes it on the next start.
Since the platform only deletes temporarily files when it is restarted, we recommend that you monitor their deletion. Otherwise, if a great number of temporary files is created, they might occupy a significant amout of disk space before the platform is restarted. Following this recommendation is especially important for script parts that are executed at server because the server usage schedule might not include regular restarts.
The lifetime of a server-based temporary file should not exceed the duration of a single server request because the next request might be directed to another physical server.
Also pay attention to algorithms that generate exceptions. In such algorithms creation and deletion of files must be performed within the Try-Except block.
Messages displayed to users
1C:Enterprise 8 provides tools for displaying messages. The UserMessage script object is intended for displaying errors, while the ShowUserNotification() method is intended for displaying notifications.
Let us consider a scenario where you need to check whether specific form fields are filled.
The platform provides a way to set up automatic fill checks for specific configuration object attributes. When a user attempts to write an object, if some of the attributes are not filled, the platform displays an error message. This message is linked to a form item. If you click the message, the focus is moved to the linked field and a balloon with the error message is displayed next to the field.
You can implement similar behavior using the script. The following example describes how automatic fill checks are performed during document posting.
When an object form starts executing the write command, control is passed to the server that converts object data into an actual object. After converting the object, the platform records the mapping between the object and the form attribute that stores object data to a location accessible for both client and server. Then the writing and posting process continues until a message must be displayed.
When an actual object "needs" to generate a message, it does not "know" it own location, it only "knows" that the attribute is not filled. So it creates a new UserMessage object, fills its Text property with the message text, and fills its Field property with the name of the unfilled attribute.
Then the following data must be passed to the form: which object generated the message and which object attribute the message is related to. This data is required for linking the message to a form item. To establish a link, the data object calls the SetData() method of the UserMessage object and passes itself as a parameter. This includes a search in available "object—form attribute name" mappings. If the object is found in the mappings, the SetData() method records the form attribute name to the DataPath property of the UserMessage object, and records the reference to the object (or register record key) to the DataKey property of the UserMessage object.
The message is now complete and ready for displaying. Once all of the server operations are completed, a package that contains messages is sent from the server to the client, and then the messages are displayed in the message window.
NOTE. While control is not yet returned to the client, you can get the array of messages using the GetUserMessages() global context method.
When a user double-clicks a message in the mesage window, the form checks the DataKey and DataPath properties. If a form has an attribute whose name matches the DataPath property value and the attribute reference matches the DataKey property value, an attempt to find the form item linked to the attribute is made. If the item is found, the focus is moved to it and the message is displayed next to the item.
If the DataKey property is filled but the form does not have an attribute whose name matches the DataPath property value, a new object form is opened and all messages having this data key (which is a reference to an object or a register record key) are displayed in this new form.
Creating messages using script
Let us recreate this behavior using script.
First, register the mapping between the object and the form attribute name. Use the SetObjectAndFormAttributeConformity() global context function, as in the following example:
&AtServer Procedure ProcedureCalledFromClient(DataObject) Document = FormDataToValue(Object, Type("DocumentObject.GoodsReceipt")); // Converting form data into an object SetObjectAndFormAttributeConformity(Document, "Object"); // Creating the mapping ObjectOperation(Document); // Opeartions on the object that might require displaying messages EndProcedure
This script fragment converts form object data to an actual object and creates a mapping between the actual object and the "Object" form attribute.
To generate a message, use the following example:
&AtServer Procedure ObjectOperation(DataObject) // Some operations that require displaying a message // Creating a message Message = New UserMessage(); Message.Text= ShortageCount + " " + ItemUnitOfMeasurement + "shortage in row 11 of the ""Items"" tabular section"; Message.Field = "Items.Count"; // Mapping the actual object to a form attribute was performed // earlier by executing the SetObjectAndFormAttributeConformity method Message.SetData(DataObject); // The message properties DataPath and DataKey are now filled // DataPath value changed from an empty string to the form attribute name // DataKey value changed from Undefined to the reference to the document // Displaying the message in the form. Message.Message(); // The message will be displayed in the form // and linked to the Count field // in the 11th row of the Items table EndProcedure;
This script fragment creates a UserMessage object that stores message Text and the object Field whose incorrect value caused the message generation. It takes the object position in the form from the "Object—FormAttributeName" mapping that was created earlier. Next, the message will be added to the form message window and linked to the corresponding form item.
IMPORTANT. The mapping between the object and the form attribute is only created for a specific object instance (not for its reference). The mapping is available until the object instance is deleted.
The platform also allows linking a message to a form manually. You might need this for displaying a message at client where object data is not available.
&AtClient Procedure ObjectOperationOnClient(DataObject) // Some operations that require displaying a message // Creating a message Message = New UserMessage(); Message.Text= ShortageCount + " " + ItemUnitOfMeasurement + "shortage in row 11 of the ""Items"" tabular section"; Message.Field = "Items.Count"; // Linking the object to the form manually Message.DataKey = DataObject.Reference; Message.DataPath = "Object"; // Displaying the message in the form. Message.Message(); // The message will be displayed in the form // and linked to the Count field // in the 11th row of the Items table ... EndProcedure;
NOTE. In both examples, the Text property refers to the 11th row while the Field value refers to the 10th row. This is because table row numbering in the user interface begins with 1, while the same numbering in the script begins with 0.
Examples of filling the Field property of the UserMessage object
Tabular section attribute
Record set attribute
1C:Enterprise script execution process
This article describes the process of 1C:Enterprise script execution and the actions the platform performs when script execution starts and stops. These actions include: performing transactions, locking and unlocking data for editing it, deleting reusable return values, and logging memory leaks.
What is 1C:Enterprise script execution process
The script execution process (hereafter - the process) is execution of configuration code, which is written in 1C:Enterprise script, by the platform.
The process starts when 1C:Enterprise platform or the operating system creates a 1C:Enterprise object whose module body is not empty, or calls a module method. Once the module body or method end is reached, the process ends.
- The process of module body execution starts when a client 1C:Enterprise application starts (if the configuration has an application module that is not empty: ordinary application module, external connection module, or managed application module). The module body is executed together with all external calls made from it. Then the module body execution process ends.
- The process starts when the platform calls application module event handlers (BeforeStart, BeforeExit) or idle handlers (set by the AttachIdleHandler method). The process ends when the handler execution ends.
- The process starts when the platform calls an interactive action handler in a form or in a control (OnActivateRow, OnActivateColumn, and so on). The process ends when the handler execution ends.
- The process starts when, after an interactive action is performed, the platform calls the action handler defined in the object module (BeforeWrite, OnWrite, and so on). The process ends when the handler execution ends.
- The process starts when execution of a server method called from a client starts. The process ends when control is returned to the client.
- The process starts when a script method is called using COM for the first time. The process ends when the method returns control for the last time.
Important: if the script method call stack has complex structure and includes multiple switches between the platform, the operating system, and the script, the process starts when the first switch from the platform or from the operating system to the script is performed. The process ends when the last switch from the script to the platform or the operating system is performed.
The only exception is the scenario when the debugger pauses script execution and calculates an expression. A new process starts for calculating the expression. The process ends when the calculation is completed.
When a process starts or stops, the platform performs a number of implicit actions, which might affect the configuration functioning.
Many configuration features require concurrent data reading and writing. This is implemented using transactions.
Transactions are started or ended implicitly by 1C:Enterprise objects (for example, when documents are written or posted), or explicitly by global context methods BeginTransaction(), CommitTransaction(), and RollbackTransaction().
A transaction start can be attempted multiple times (implicitly or by using BeginTransaction()). The transation starts at the first attempt; the next attempts increment the transaction counter. An attempt to end a transaction (implicitly or by using CommitTransaction() or RollbackTransaction()) decrements the transaction counter. The last attempt ends the transaction.
Important: starting a process does not reset transaction counters.
While a process is running, the transaction counter value cannot become less than the stored value. If the execution of CommitTransaction() or RollbackTransaction() method would decrease it further, a "No open transactions" exception is raised.
When a process ends, if the transaction counter value is greater than the stored value, the platform implicitly calls RollbackTransaction() multiple times to make these values equal.
Data locks required for editing
You can manage data locks required for editing using the following methods: LockDataForEdit() and UnlockDataForEdit(). If you call the method LockDataForEdit() without the FormID parameter, the platform releases the lock implicitly when the process ends.
Deleting reusable return values
Common modules might store frequently used values. If the Reuse return values property of a module is set to During call, the stored values are implicitly deleted when the process ends.
Logging memory leaks
The technological log provides the option to monitor specific script fragments for memory leaks during their execution. In particular, it is important to monitor processes for memory leaks. In the following example the technological log configuration file contains the "leaks" element, which is responsible for monitoring memory leaks.
<leaks Collect=1> <point Call="client"/> <point Сall="server"/> </leaks>
In this example the following data is recorded to the technological log: objects that were created but were not released between the start and end of the process on the server (<point Сall="server"/>) and on the client (<point Call="client"/>).
Specifics of reusing return values
This article applies to common modules that have Server and Server call properties set to True and the Reuse return values property set to During session.
When export methods declared in the module are called from the client application, return values are cached both on the client and on the server.
During the call, the platform searches the client cache for the return value.
- If the value is found, this value is returned.
- If the value is not found in the client cache, a server call is performed and the platform searches the server cache for the return value.
- If the value is found, this value is returned.
- If the value is not found in the server cache, the module body is executed, the result is stored to the server cache, sent to the client, stored to the client cache, and returned to the call source.
This can introduce data inaccuracies in scenarios where cached objects are changed by the client script. An object is changed in the client cache but it is not changed in the server cache. If the object in the client cache becomes obsolete, it is retrieved from the server, possibly from the server cache instead of executing the module body.
During confugration development, do not count on automatic updates of cached objects. Use the RefreshReusableValues method for forced cache updates.
If your decide to change certain objects in caches, use one of the following methods to avoid inaccuracies:
- update cached data on the client and on the server simultaneously;
- for modules with Reuse return values property set to During session, set the Server, Client, and possibly External connection properties to True, and set the Server call property to False.