In such a case the component must ensure that only a single instance execute at a time, independent of whether or not an instance can be reused: a reusePolicy of “any” or “none” can still produce multiple concurrently executing instances of the component execution class. A component may define an integer property named “maxConcurrent.” The value of this property determines how many concurrent instances of the component can execute at the same time. If not specified, the default value of this property is unlimited. However, if the component metamodel XML defines this property with the value of “1,” the system will guarantee that only one instance of the component executes at a time. As a result, code that is not thread-safe with respect to static data will execute without data corruption from multiple executing component instances. When maxConcurrent is set to “1,” only a single execution of the component can proceed at a time. If there are multiple requests for the component to execute at the same time, they are queued and only a single execution will be allowed run. For highly concurrent and shared systems such as the Fiper distributed execution environment, the setting can cause a severe bottleneck and affect overall system throughput and performance, particularly for long-running components. The setting is not recommended unless it is required for proper execution behavior of the component. In a Fiper environment, maximum concurrency of one is per JVM (each station can have one copy). If run-as security is activated, then maximum concurrency is one copy per user per station. Process components must never have a maxConcurrent value of “1” because it can cause any model that uses the component in a nested fashion to deadlock. For example in the following model, the same component type is used at two levels of the model. If the process component had a maxConcurrent of “1,” the model would stop executing when it reached the second level of the model:
The maxConcurrent setting can be combined with the reusePolicy setting. When maxConcurrent is set to “1” and reusePolicy to “any,” the system will create only a single instance of the class for all executions of the component. Only a single execution will proceed at a time; when it completes, the same class instance will be used to run the next execution. The component and any native code it calls must be serially reusable to support this mode of execution. For more information, see Reusable and Persistent Components. In general, native code will not be serially reusable if it depends on the initial value of static data that are subsequently updated when the code runs. The next execution of the code will find the static data with prior values instead of the initial values. For more information, see Use of Native Code. When a maxConcurrent setting of “1” is combined with a reusePolicy
of “none,” a new component instance will be created for each execution
of the component. When the execution is complete, the Use of Native CodeWhen a component uses native code, the Java Virtual Machine will load and statically initialize the native code just one time. Even if the component class instance is dereferenced and even if garbage collected, the JVM does not unload the native code. The next use of the component, even if a new Java class instance is created, will continue to use the already loaded native code and static data. The reusePolicy affects only the reuse of Java classes and does not affect native code. As a result, any native code used in any component or plug-in must be serially reusable. In addition, the native code must not depend on the initial value of static data that is subsequently modified during execution. Such code must be modified to initialize static data explicitly for every execution of the component. It is difficult to write Fortran code that can be safely used in an Isight component because most Fortran compilers create COMMON blocks as initialized data that are never reset once the program starts running. Special compiler options must be set when compiling Fortran code for use in a shared library called from Java to cause the compiler to store local variables on the stack instead of in a BLANK COMMON area. There is a restriction on native code, not the Java code, because there can be only one instance of the native code that will be shared and reused by all instances of the Java component execution class.
The above limitation on native code applies only to native code that is directly invoked from Java through the Java Native Interface (JNI). It does not apply to native code that is launched as a separate process. The following table summarizes the recommended settings based on the nature of the Java component executor class and the native code it invokes. If not specified in the component XML descriptor, the default settings are unlimited maxConcurrent and a reusePolicy of “none.”
Thread-Safe Java Component. Has no Java STATIC data or uses thread synchronization when accessing STATIC data. Does not have to protect against multiple threads of execution in the same instance because system will always use a single thread for a component instance. Serially Reusable Java Component. Has no class fields or resets the class field values for each execution. Thread-Safe Native Code. Supports multiple concurrent threads of execution with appropriate protection of any data that is not allocated dynamically. Serially Reusable Native Code. Resets—or does not depend—on the value of all storage before use. Static storage can be used if not modified during execution or if the static values are reset before use. Job-Level PersistenceOccasionally, a component execution class may deliberately retain or accumulate data from one execution to the next. In general, retention of data is not recommended because it greatly restricts the flexibility of the infrastructure to assign component instances and computers for execution and can cause memory growth and have other unintended side effects. In addition, the system makes few guarantees about where and how a component is executed, so the component developer cannot make assumptions about how or if a given component instance will be reused. Such special case components usually accumulate data associated with their use in a particular model. For example, some calculated data may be held from one execution to the next, with the assumption that each execution is part of the same job. In general, that assumption is not valid; the system may reuse a component instance for different jobs at any time, as well as, interleave executions from many jobs. However, the system does provide a means to declare that a component instance can be “scoped” to a single job. That is, the instance will be restricted to running only within the job in which it is first used. All subsequent executions of the component will be part of that same job. When the job ends, the component may be reused for another job if the reusePolicy is “any” or the component will be destroyed if the reusePolicy is “none.” Designating a component scoped to a job is done by defining a component property named “reuseScope” with the value of “job.” When a component has the reuseScope value of “job,” the maxConcurrent value still controls how many concurrent instances of the execution class will be used at the same time. If maxConcurrent is “1,” the single instance will be restricted to the first job in which it is used. Any request to execute the component from some other job will be queued until the first job completes, which can have a major impact on overall system throughput because it forces one job to wait for another job to complete. The component instance cannot be released until the entire job in which it was used completes, which may be a long time after the component execution is done. Therefore, it is highly recommended that components with a reuseScope of “job” have a maxConcurrent greater than “1,” which also implies they must be thread-safe. In general, specifying a reuseScope of “job” on a component does not mean that every execution of that component in the job will use the same class instance. In the distributed SIMULIA Execution Engine execution environment, the component may be dispatched to different SIMULIA Execution Engine stations for execution. In this case each station will have a (dedicated) instance for the job. Unless some other means such as affinities are used to control the dispatching locations, the component cannot assume that a single instance will process every execution within a job. Job level scoping reserves an instance of the component execution class for the job. If the same component (type) is used in multiple places in the model, the same instance will be used for all of them. For example, the same component can be used at several levels of the model or in multiple places in the simulation process flow. With job-level persistence, the component execution instance will be used for all of them because they run as part of the same job. If each of the unique usages of the component within the model needs to have a separate instance, use the reuseScope setting of “job-comppath” as described in Job-Component Path Persistence. Job-Component Path PersistenceThe job level scoping described in the prior section will use a single instance to execute all uses of a particular component type within a model. For example, the following model has three usages of the Calculator type component named C1, C2, and C3. If the Calculator component had a reuseScope of “job,” a single instance would be used whenever C1, C2, or C3 were to be executed (assuming they were all dispatched to the same SIMULIA Execution Engine station). If you want each unique use of the component in the model to have its own instance, a reuseScope of “job-comppath” can be specified. The scope of the component instance is a unique component path within the model. Each use of the Calculator component in the example above has a unique path from the root (e.g,. C1 has a path of “Task1.C1,” component C2 has a path of “Task1.DOE.C2,” etc.). Similar to job level persistence, each instance will be reserved for the job and the unique component path within that job. A maxConcurrent value of “1” must never be used with a reuseScope of “job-comppath” because a model such as that shown above would logically deadlock. The execution of C1 would reserve a component instance; and when it came time to execute C2, the system would be unable to create another component instance because of the maxConcurrent limit. The execution of C2 would wait indefinitely for the component instance to become available. It cannot become available until the job ends, so the execution of C2 would be blocked indefinitely. Component Cancel and TimeoutIt is possible that the system might need to stop the execution of a component before the lifecycle methods have completed. For example, the user might have chosen to cancel the job in which the component is executing, or the component might have exceeded its maximum execution time (as configured in the model). The component might also be stopped for other reasons. The system uses several mechanisms when attempting to stop an executing
component. In general, the component is inside the When the system has determined that a component execution needs to be stopped, it takes the following steps:
It is important to try to design component execution code to be responsive
to system stop requests. Either the If a process
component is blocked waiting for some system event—e.g., waiting for
a subflow to complete through the If an public void execute(RuntimeEnv runEnv) throws RtException { try { // This might loop for a long time... for (...) { // Do some work, less than 10 seconds long if (Thread.interrupted()) { throw new RtInterruptedException("My Component was interrupted."); } } } catch (RtException rte) { throw rte; // No need to wrap RtExceptions } catch (Throwable t) { throw new RtException(t, "My component failed to execute."); } } Components which use Runtime Message ListenersA component may need to be notified of important events even while
it is executing (e.g., even while inside the To do this, the component class must implement the RuntimeMessageListener interface. This interface has a single method with the signature: public void runtimeMessageReceived(RuntimeMessage msg) throws RtException; The component must then register itself as a listener for runtime messages
by calling the Unlike the component lifecycle methods, this method is called on an
asynchronous thread. It may be called at any time, including during the
time that another thread is executing some other method on the component,
such as the It is also possible that this method will be called from multiple threads at the same time to deliver multiple messages. The implementation must ensure proper thread synchronization in all cases. When the system has a message to deliver to the component, it will
call this method with a RuntimeMessage argument that indicates the nature
of the message. The type of message can be determined by the
This method is designed to throw Subflow Result ListenersProcess-type
components submit requests to the system to run the subflow below them
in the model tree using the The component can then wait for all the subflows to complete by using
the The component can register itself as a “listener” for subflow completion event messages and implement the SubflowListener interface so that it can request that subflow results be delivered to the component as soon as each one completes. The component can then examine results as they become available without waiting for the slowest subflow to complete. This interface has a single method with the signature void subflowCompleted(SubflowResults results) throws RtException; The component must also register itself as a listener by making a call
to the This method will then be called once for each subflow after the subflow completes. Subflows often complete in a different order from how they were submitted. The SubflowResults object contains information about the status of the subflow (success/failure) and, if it was successful, the resulting parameter values. Unlike the component lifecycle methods, this method is called on a
separate asynchronous thread. The component developer is responsible
for synchronizing any data shared with the thread running the Each event, or call to this method, can may be made on a different thread; but the system guarantees that this method will only be called serially. For example, only one thread will be in this method at a time, but each call to this method may be from a different thread (i.e., this method should make no assumptions about the identity of the thread which calls it). This listener method is free to use nonthread safe data structures
as long as those structures are not also accessed from the The component can throw an The following sample Component submits a set of subflows to execute and then processes each subflow as it completes by writing a message to the job log. public class MyComponent extends AbstractComponent implements SubflowListener { private RuntimeEnv env; // For event method to use public void execute(RuntimeEnv runEnv) throws RtException { try { env = runEnv; // Make available to event method // Run 10 subflows in parallel for (int i=0; i<10; i++) { Context ctx = runEnv.getExecutor().createSubflowContext(); //...modify the subflow parameter values... runEnv.getExecutor().runSubflow(ctx); } // Wait for all of them to complete before returning runEnv.getExecutor().waitForSubflow(); } catch (Throwable e) { throw new RtException(e, "Failed to execute my component."); } finally { // Always cleanup class field object references env = null; } } public void subflowCompleted(SubflowResults results) throws RtException { if (results.getCc() == PSEUtils.WORK_CC_OK) { env.getJobLog().logWarn("Run number "+results.getRunNumber()+" completed OK."); } else { env.getJobLog().logWarn("Run number "+results.getRunNumber()+" failed."); } } } The system guarantees that any thread blocked on |