Skip to main content

Shared Data Server Rendezvous Points (Synchronization)

This is an example of how to use Shared Data Server to synchronize virtual user scripts, or cause multiple virtual users to wait for a rendezvous point before continuing script execution.

This utilizes the fact that calling the Get SDS method - for a key which is not currently present in the Shared Data Server - will wait until the key becomes available before returning.

Pair this with the Increment SDS method which can be used to count how many virtual users have reached a certain point in their script execution, and it is straightforward to create certain trigger conditions.

Running Shared Data Server

See Starting and Stopping the Shared Data Server for more details.

Test Controller Server can automatically launch the SDS application as part of the test startup procedure. To do this:

  • Select a test from the Tests folder in the Project tree in Studio
  • Enable the Start the Shared Data Server at the beginning of the test option on the Test View > Shared Data Server tab

Setting the "Start the Shared Data Server at the beginning of the test" checkbox

Connecting to Shared Data Server

In your virtual user script, you must first instantiate the SharedData class in order to connect to the running Shared Data Server. We store the object in a member variable of the my_script class so it can be referenced in any of the methods in this script.

warning

It's also a good idea to set the maximum number of connections to the number of virtual users who will be trying to connect simultaneously.

my_script.java
public class my_script extends com.facilita.fc.runtime.VirtualUserScript
{
private SharedData sharedData;

@Override
public void pre() throws Exception
{
//do not remove following line
super.pre();

this.sharedData = new SharedData(getString("sds_host", "localhost"), getInt("sds_port", 5099));
SharedData.setMaxConnections(this.getGroupSize());
}
...

The host and port of the Shared Data Server are unlikely to change frequently, but it is good practice to use the data dictionary API methods to retrieve them using the sds_host and sds_port keys, specifying default values. This enables them to be easily changed later using global variables.

note

If you are going to access the Shared Data Server from more than one virtual user script, this code could go in a custom virtual user

Incrementing the vus_ready counter

In this example, a shared variable called vus_ready is used to count the number of virtual users who have reached the rendezvous point. Each virtual user will increment the value by one, and synchronization occurs when all the virtual users in the group are ready.

my_script.java
public class my_script extends com.facilita.fc.runtime.VirtualUserScript
{
...

@Override
public void script() throws Exception
{
//TODO Place your script variables here.

//TODO Place your iterated script code here.
this.sharedData.increment("vus_ready", 1);

if (this.sharedData.getInt("vus_ready", 0) == this.getGroupSize())
{
this.sharedData.set("rendezvous", "go");
}
}

Specific example: Eggplant Functional virtual users

The example described on this page puts the Shared Data Server connection in the pre() method and the rendezvous code inside the script() method. Your use case may require you to put this code elsewhere. For example, an Eggplant Functional virtual user script is automatically generated with the following structure:

  • pre() - Set up connection properties and call Connect() to establish connection from Eggplant Functional to the SUT
  • script() Use RunWithNewResults() to execute an Eggplant Functional SenseTalk script

If your use case would benefit from the virtual users waiting for the rendezvous point before they establish the connection to the SUT, then put the rendezvous code in the pre() method as needed:

Example Eggplant Functional script code

epf_suite_script.java
public class epf_suite_script extends com.facilita.fc.eggPlant.EggplantVirtualUserScript
{
private SharedData sharedData;

@Override
public void pre() throws Exception
{
//do not remove following line
super.pre();

this.sharedData = new SharedData(getString("sds_host", "localhost"), getInt("sds_port", 5099));
SharedData.setMaxConnections(this.getGroupSize());

if (getBoolean("connectToSUT", true))
{
// Connect to the System Under Test.
ConnectionProperties connectionProperties = new ConnectionProperties();
connectionProperties.setName(getString("sutHost"));
connectionProperties.setPort(getInt("sutPort", 5900));
...

this.sharedData.increment("vus_ready", 1);

// Wait for the rendezvous point (set by REST API)
this.sharedData.get("rendezvous", -1);

Connect(connectionProperties);

setBoolean("connectToSUT", false);
}
}

@Override
public void script() throws Exception
{
// Run the Eggplant Functional script
RunWithNewResults("\"epf_suite/script\"");
}
}

Signalling the other virtual users

The previous example already hinted at the method used to signal the other virtual users - another shared variable called rendezvous. The key concepts here are:

  • The rendezvous key does not exist in the Shared Data Server until it is set
  • A timeout of -1 is passed to the Get SDS method, which will cause the virtual user to wait at that line of code until the rendezvous variable exists in the Shared Data Server.
  • When the rendezvous key is created with a value, all waiting virtual users continue script execution.

If you don't want the virtual users to wait forever, you can pass a large timeout value in milliseconds instead.

Signalling from within the virtual user script

After incrementing the vus_ready variable, the virtual user script can check whether the current count is the same as the size of the virtual user group - i.e. all virtual users in the group are ready. You may want to change this logic based on your own scripting needs.

my_script.java
public class my_script extends com.facilita.fc.runtime.VirtualUserScript
{
...

@Override
public void script() throws Exception
{
//TODO Place your script variables here.

//TODO Place your iterated script code here.
this.sharedData.increment("vus_ready", 1);

if (this.sharedData.getInt("vus_ready", 0) == this.getGroupSize())
{
this.sharedData.set("rendezvous", "go");
}

// Wait for the rendezvous point (set by last VU)
this.sharedData.get("rendezvous", -1);

// Continue script code...
}
}

Signalling from an external source

If the test is being controlled from an external source, such as a CI/CD pipeline, the logic to check when virtual users are ready can be implemented using the Test Controller REST API.

In this case, the virtual user script would simply contain:

my_script.java
public class my_script extends com.facilita.fc.runtime.VirtualUserScript
{
...
@Override
public void script() throws Exception
{
//TODO Place your script variables here.

//TODO Place your iterated script code here.

this.sharedData.increment("vus_ready", 1);

// Wait for the rendezvous point (set by REST API)
this.sharedData.get("rendezvous", -1);

// Continue script code...
}
}

The external script can use the Virtual User Group Counts method to find out how many virtual users to wait for, as well as the Shared Data Server Get and Set methods.

Here is an example of such a script, written in Python and using the Requests module:

import sys
import time

import requests

TC_URL = "http://localhost:5001/test_controller/api/1.0/"
MAX_RETRIES = 60

# Get the number of VUs we are waiting for (replace workspace/project/test names with your own)
response = requests.get(TC_URL + "my_workspace/my_project/my_test/virtual_user_group_counts")

if response.status_code == 200:
vu_count = response.json().get("total_count")
else:
print("Failed to get virtual user group count")
sys.exit(1)

# Wait for the VUs to become ready
for x in range(MAX_RETRIES):

response = requests.get(
TC_URL + "shared_data_server/get",
params={"key": "vus_ready"}
)

if response.status_code == 200:
vus_ready = response.json().get("vus_ready", 0)

print(f"Attempt {x+1} - {vus_ready} of {vu_count} VUs are ready")

# Check if all VUs are ready
if vus_ready == vu_count:
print("Setting rendezvous trigger variable")

requests.post(
TC_URL + "shared_data_server/set",
json={"key": "rendezvous", "value": "go"},
)

# Break out of the retry loop
break

time.sleep(1)

Complete example VU script code

This is the code from the examples above, as it would appear in a virtual user script file:

my_script.java
// Script Generator Version - NOT generated (script specification)
package com.testplant.testing;

import java.time.Duration;
import java.time.ZoneOffset;
import java.util.EnumSet;
import java.util.List;
import java.util.ArrayList;

import com.facilita.fc.runtime.*;
import com.facilita.fc.runtime.backgroundScripting.*;
import com.facilita.util.*;
import com.facilita.exception.*;
import com.facilita.fc.web.*;
import com.facilita.fc.jni.*;

public class my_script extends com.testplant.testing.VirtualUserScript
{
private SharedData sharedData;

@Override
public void pre() throws Exception
{
//do not remove following line
super.pre();

this.sharedData = new SharedData(getString("sds_host", "localhost"), getInt("sds_port", 5099));
SharedData.setMaxConnections(this.getGroupSize());
}

@Override
public void script() throws Exception
{
//TODO Place your script variables here.

//TODO Place your iterated script code here.
this.sharedData.increment("vus_ready", 1);

if (this.sharedData.getInt("vus_ready", 0) == this.getGroupSize())
{
this.sharedData.set("rendezvous", "go");
}

// Wait for the rendezvous point (set by last VU)
this.sharedData.get("rendezvous", -1);

// Continue script code...
}
}