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.
More details on the relevant SDS methods can be found on the following API documentation pages:
- Java
- C#
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
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.
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.
- Java
- C#
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());
}
...
public class my_script:AVirtualUserScript
{
private SharedData sharedData;
public override void Pre()
{
// Do not remove following line
base.Pre();
this.sharedData = new SharedData(GetString("sds_host", "localhost"), GetInt("sds_port", 5099));
SharedData.MaxConnections = this.GroupSize;
}
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.
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.
- Java
- C#
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");
}
}
public class my_script:AVirtualUserScript
{
...
public override void Script()
{
//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.GroupSize)
{
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 callConnect()
to establish connection from Eggplant Functional to the SUTscript()
UseRunWithNewResults()
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
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 theGet
SDS method, which will cause the virtual user to wait at that line of code until therendezvous
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.
- Java
- C#
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...
}
}
public class my_script:AVirtualUserScript
{
...
public override void Script()
{
//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.GroupSize)
{
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:
- Java
- C#
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...
}
}
public class my_script:AVirtualUserScript
{
...
public override void Script()
{
//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:
- Java
- C#
// 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...
}
}
// Script Generator Version - NOT generated (script specification)
using System;
using System.Collections.Generic;
using System.Text;
using Facilita.Native;
using Facilita.Web;
using Facilita.Fc.Runtime;
using Facilita.Fc.Runtime.BackgroundScripting;
using AVirtualUserScript = com.testplant.testing.VirtualUserScript;
namespace com.testplant.testing
{
public class my_script:AVirtualUserScript
{
private SharedData sharedData;
public override void Pre()
{
// Do not remove following line
base.Pre();
this.sharedData = new SharedData(GetString("sds_host", "localhost"), GetInt("sds_port", 5099));
SharedData.MaxConnections = this.GroupSize;
}
public override void Script()
{
//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.GroupSize)
{
this.sharedData.Set("rendezvous", "go");
}
// Wait for the rendezvous point (set by last VU)
this.sharedData.Get("rendezvous", -1);
// Continue script code...
}
}
}