PVXS Architecture¶
This document provides an overview of the PVXS architecture, design principles, and internal structure.
Overview¶
PVXS is a modern C++ library implementing the PVAccess (PVA) protocol for EPICS. It provides three main layers:
Data Layer - Type-safe data containers (
pvxs::Value)Network Layer - PVAccess protocol implementation
Application Layer - Client and Server APIs
┌─────────────────────────────────────────────────────────────┐
│ Application Layer │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ Client │ │ Server │ │
│ │ API │ │ API │ │
│ └──────────────┘ └──────────────┘ │
│ │ │ │
│ └──────────────┬───────────────┘ │
│ │ │
│ ┌──────▼──────┐ │
│ │ Data │ │
│ │ Layer │ │
│ │ (Value) │ │
│ └──────┬──────┘ │
└────────────────────────┼────────────────────────────────────┘
│
┌────────────────────────┼────────────────────────────────────┐
│ ┌──────▼──────┐ │
│ │ Network │ │
│ │ Layer │ │
│ │ (PVAccess) │ │
│ └──────┬──────┘ │
└────────────────────────┼────────────────────────────────────┘
│
┌────────────────────────┼────────────────────────────────────┐
│ ┌──────▼──────┐ ┌──────────────┐ │
│ │ EPICS Base │ │ libevent │ │
│ │ (OSD) │ │ (Networking)│ │
│ └─────────────┘ └──────────────┘ │
└─────────────────────────────────────────────────────────────┘
Design Principles¶
1. Type Safety¶
PVXS eliminates the need for explicit downcasting and NULL pointer checks common in pvDataCPP. The Value class uses a single type that handles all data types internally.
Before (pvDataCPP):
PVStructurePtr top = ...;
PVIntPtr value = top->getSubField<PVInt>("value");
if(!value)
throw ...;
int32_t val = value->get();
After (PVXS):
Value top = ...;
int32_t val = top["value"].as<int32_t>(); // Throws if missing or wrong type
2. Exception-Based Error Handling¶
PVXS uses exceptions for error handling rather than return codes, making error paths explicit and reducing the chance of ignored errors.
3. Functor-Based Callbacks¶
Instead of requiring interface classes (as in pvAccessCPP), PVXS uses C++ functors/lambdas for callbacks, reducing boilerplate code.
Before (pvAccessCPP):
struct MyCallback : public pvac::GetCallback {
void getDone(const GetEvent& evt) override { ... }
};
MyCallback cb;
chan.get(&cb);
After (PVXS):
ctxt.get("pv:name")
.result([](Result&& r) { ... })
.exec();
4. Automatic Change Tracking¶
Change tracking is built into the Value class, eliminating the need for separate BitSet objects.
Before (pvDataCPP):
PVStructurePtr top = ...;
BitSetPtr changed(new BitSet(...));
value->put(42);
changed->set(value->getFieldOffset());
After (PVXS):
Value top = ...;
top["value"] = 42; // Automatically marked as changed
assert(top["value"].isMarked());
5. Modern C++ Features¶
PVXS leverages C++11 features:
Move semantics for efficient transfers
Smart pointers for automatic memory management
RAII for resource management
Type traits for compile-time checking
Architecture Components¶
Core Components¶
Value Container (
pvxs::Value)Type-safe data structure representation
Supports all EPICS scalar and array types
Built-in change tracking
Efficient array handling with
shared_array
Client Context (
pvxs::client::Context)Manages connections to PVAccess servers
Handles server discovery
Provides Get, Put, Monitor, RPC operations
Automatic reconnection
Server Instance (
pvxs::server::Server)Listens for client connections
Manages multiple data sources
Handles concurrent client requests
Automatic beaconing for discovery
SharedPV (
pvxs::server::SharedPV)Represents a single PV served by a server
Mailbox mode for simple storage
Custom handlers for Put/RPC operations
Supports multiple concurrent subscribers
Source (
pvxs::server::Source)Abstract interface for PV sources
Allows dynamic PV registration
Used by IOC integration
Data Layer¶
Value Container¶
The Value class is the central data container in PVXS. It represents a single data structure, field, or array element.
See :doc:api/value for detailed API documentation.
Key Features:
Type Safety: Compile-time and runtime type checking
Change Tracking: Automatic marking of modified fields
Efficient Access: Direct field access via
operator[]Type Conversion: Safe conversions between compatible types
Array Support: Efficient handling of arrays with
shared_array
Structure:
Value
├── Type information (TypeCode)
├── Data storage (variant-based)
├── Change tracking (BitSet-like, internal)
├── Field metadata
└── Children (for structures)
Example Usage:
// Create an NTScalar structure
Value pv = nt::NTScalar{TypeCode::Float64}.create();
// Set value (automatically marked as changed)
pv["value"] = 42.0;
// Access value with type conversion
double val = pv["value"].as<double>();
// Check if field is marked
if(pv["value"].isMarked()) {
// Field has been modified
}
// Clone with empty structure
Value empty = pv.cloneEmpty();
// Iterate over fields
for(Value field : pv.ichildren()) {
std::cout << pv.nameOf(field) << std::endl;
}
Type System¶
PVXS supports all EPICS data types:
Scalar Types:
TypeCode::Int8,Int16,Int32,Int64TypeCode::UInt8,UInt16,UInt32,UInt64TypeCode::Float32,Float64TypeCode::StringTypeCode::Bool
Array Types:
Arrays of any scalar type
Efficient storage with
shared_array<T>
Structures:
Nested structures
Union types
Variant unions
Normative Types:
NTScalar- Scalar with metadataNTScalarArray- Array with metadataNTEnum- EnumerationNTTable- Table/tableNTNDArray- ND array (for areaDetector)NTURI- URI structure
Network Layer¶
PVAccess Protocol¶
PVXS implements the PVAccess protocol version 1.x, providing:
Operations:
GET_FIELD (Info) - Query PV structure
GET - Fetch present value
PUT - Update PV value
RPC - Remote procedure call
MONITOR - Subscribe to updates
SEARCH - Server discovery (UDP)
Protocol Features:
Binary encoding (efficient)
Field selection (partial updates)
Flow control (for Monitor)
Compression support
Authentication framework
Connection Management¶
Client Side:
Automatic server discovery via UDP broadcast
Connection pooling (reuse connections)
Automatic reconnection on failure
Connection health monitoring
Server Side:
Accept multiple concurrent connections
Per-connection state management
Connection lifecycle tracking
Graceful shutdown
Discovery Mechanism¶
UDP Search (Client → Server)
Client broadcasts search request on port 5076
Request includes PV name pattern
Servers matching pattern respond
UDP Beacon (Server → Client)
Server periodically broadcasts beacon
Beacon includes server address and port
Clients can listen to discover all servers
Client Architecture¶
Client Context¶
The Context class manages all client-side operations:
See :doc:api/client for detailed API documentation.
Context
├── Config (network settings)
├── Connection Manager
│ ├── Active connections
│ ├── Connection pool
│ └── Discovery handler
├── Operation Manager
│ ├── Pending operations
│ └── Operation lifecycle
└── Event Loop Integration
└── libevent integration
Key Responsibilities:
Server discovery and connection management
Operation lifecycle (Get, Put, Monitor, RPC)
Error handling and retry logic
Thread safety for concurrent operations
Operation Flow (Get Example)¶
1. Client: ctxt.get("pv:name")
└── Creates GetBuilder
2. Client: .exec()
└── Starts operation
└── Checks connection pool
3. If no connection:
├── Send UDP search
├── Wait for server response
└── Establish TCP connection
4. Send GET request
└── Includes pvRequest (field selection)
5. Wait for response
└── Parse response
└── Create Value from data
6. Invoke callback or return result
└── result() callback (if set)
└── or wait() return
Builder Pattern¶
Operations use a builder pattern for configuration:
auto op = ctxt.get("pv:name")
.pvRequest("field(value,alarm)")
.result([](Result&& r) {
Value v = r();
// Process value
})
.exec();
// op can be stored and canceled later
This allows:
Fluent API
Optional configuration
Delayed execution
Operation cancellation
Server Architecture¶
Server Structure¶
See :doc:api/server and :doc:api/sharedpv for detailed API documentation.
Server
├── Config (network settings)
├── Network Listener (TCP)
├── Beacon Sender (UDP)
├── Source Manager
│ ├── __builtin (StaticSource)
│ ├── __server (server info)
│ └── User Sources
└── Connection Handler
├── Per-connection state
└── Request routing
Source System¶
Sources provide a pluggable architecture for PV registration:
Built-in Sources:
__builtin- Static PVs added viaaddPV()__server- Server information PV
IOC Source:
QSRV2 - Database record integration
Custom Sources:
User-defined sources implementing
SourceinterfaceDynamic PV registration
Priority-based ordering
Request Handling Flow¶
1. Client connects (TCP)
└── Server creates connection handler
2. Client sends request (GET/PUT/etc.)
└── Parse request
└── Extract PV name and pvRequest
3. Route to appropriate Source
└── Check Source priority
└── Call Source::onCreate()
4. Source creates ConnectOp
└── Validate pvRequest
└── Return prototype (data type)
5. Operation execution
└── For GET: Source provides data
└── For PUT: Source receives data
└── For MONITOR: Setup subscription
6. Send response
└── Serialize data
└── Send to client
IOC Integration¶
QSRV 2¶
QSRV 2 (Quick Server 2) provides high-level IOC integration:
IOC Database
│
├── Single PV Access
│ └── Direct database record access
│
├── Group PV Access
│ └── JSON-defined groups
│ └── Efficient batch operations
│
└── PVA Links
└── Link database records via PVAccess
Features:
Automatic database record serving
Group-based operations (reduce network traffic)
Access security integration
Support for all database field types
Integration Points¶
Database Hooks
Intercepts database record operations
Provides PVAccess interface
Maintains synchronization
IOC Shell Commands
pvxsr()- Server reportpvxsl()- List PVspvxsi()- Version infopvxgl()- Group information
Configuration
Environment variables (
$EPICS_PVA_*)Database configuration
Access security rules
Threading Model¶
Client Threading¶
Thread Safety:
Contextis thread-safe for concurrent operationsMultiple threads can call
get(),put(), etc. simultaneouslyOperations are independent
Event Loop:
Uses libevent for async I/O
Single event loop per Context (by default)
Operations complete in event loop thread
Callbacks execute in event loop thread
Blocking Operations:
wait()blocks calling threadSuitable for synchronous code
Use with care in event-driven code
Server Threading¶
Accept Thread:
Accepts new connections
Creates connection handlers
Worker Threads:
Handle client requests
Execute user callbacks
Send responses
Callback Threading:
PUT/RPC handlers execute in worker threads
User code must be thread-safe
SharedPV access is internally synchronized
Best Practices¶
Avoid Blocking in Callbacks
Keep callbacks short
Defer heavy work to separate threads
Context Lifetime
Keep Context alive during operations
Don’t destroy while operations are pending
Value Sharing
Values are not thread-safe by default
Copy or synchronize when sharing between threads
Memory Management¶
Smart Pointers¶
PVXS uses modern C++ memory management:
std::shared_ptr- Shared ownershipstd::unique_ptr- Exclusive ownershipRAII - Automatic cleanup
Value Storage¶
Structure:
Values use efficient storage (variant-based)
Arrays use
shared_arrayfor zero-copyStructures share type definitions
Ownership:
Values can be moved (not copied when possible)
Arrays use reference counting
Type definitions are shared/immutable
Memory Patterns¶
Efficient Patterns:
// Move Value (no copy)
Value v1 = ...;
Value v2 = std::move(v1);
// Reuse structure
Value template = ...;
Value instance = template.cloneEmpty();
Avoid:
// Unnecessary copies
Value v2 = v1; // Prefer std::move(v1) if v1 no longer needed
// Holding references to temporary Values
Value& ref = createValue(); // Dangerous!
Protocol Implementation¶
Message Encoding¶
Binary Format:
Efficient binary encoding
Network byte order (big-endian)
Variable-length encoding for integers
String encoding (UTF-8)
Message Structure:
Message Header
├── Command ID
├── Payload size
└── Payload
├── Request ID
├── PV name
├── pvRequest
└── Data (for PUT/RPC)
Field Selection¶
pvRequest Format:
field(value) # Single field
field(value,alarm) # Multiple fields
field(value)field(extra) # Multiple selections
Implementation:
Parse pvRequest into field mask
Filter data before encoding
Reduces network traffic
Flow Control (Monitor)¶
Mechanism:
Client specifies queue size
Server respects queue limits
Backpressure when queue full
Implementation:
Per-subscription queue
Drop oldest or reject new (configurable)
Automatic flow control messages
Comparison with Other EPICS Modules¶
vs. pvDataCPP¶
pvDataCPP:
Class hierarchy (PVField base class)
Explicit downcasting required
Separate BitSet for change tracking
Interface classes for callbacks
PVXS:
Single Value class
Type-safe access
Built-in change tracking
Functor-based callbacks
vs. pvAccessCPP¶
pvAccessCPP:
Interface classes for callbacks
More verbose code
Older C++ style
PVXS:
Functor-based (lambdas)
Builder pattern
Modern C++ (C++11+)
Cleaner API
vs. Channel Access (CA)¶
CA:
Older protocol
Less structured data
Different API model
PVXS (PVAccess):
Modern protocol
Rich structured data
More efficient
Better suited for complex data
Detailed API Documentation¶
This architecture document provides a high-level overview. For detailed API documentation, see:
Core APIs:
:doc:
api/value- Detailed Value class documentation, field lookup, iteration, arrays:doc:
api/client- Detailed Context, GetBuilder, PutBuilder, MonitorBuilder, RPCBuilder documentation:ref:
get-info <api/client:get-info>- Get/Info Operations:ref:
clientputapi <api/client:clientputapi>- Put Operations:ref:
clientmonapi <api/client:clientmonapi>- Monitor Operations:ref:
clientrpcapi <api/client:clientrpcapi>- RPC Operations
:doc:
api/server- Detailed Server, Config, Source documentation:doc:
api/sharedpv- SharedPV and operation handlers
Comparisons:
:ref:
comparison-with-pvdatacpp <api/overview:comparison-with-pvdatacpp>- Detailed comparison examples:ref:
comparison-with-pvaccescpp <api/overview:comparison-with-pvaccescpp>- API design differences
Integration:
:doc:
api/ioc- Detailed IOC hooks and integration:doc:
reference/qgroup- Database integration details
Implementation:
:doc:
reference/netconfig- Network protocol implementation:doc:
examples/example- Source code examples
Additional Resources¶
:doc:
api/overview- Complete API documentation overview
Acknowledgments¶
This architecture documentation was created and organized by K. Gofron, Oak Ridge National Laboratory, December 28, 2025.
Note: This architecture document provides a high-level overview. For detailed API documentation, see the online documentation.