I'm embarrassed to say that I'm not entirely sure exactly what the right question to ask is...
So, for now, what's the best/correct way to improve performance of systems using the thread-per-connection model? Here's a description of our architecture and the problems we're seeing...
We have a peer-to-peer system, partially based on the architecture that Mark described in his (excellent!) article on Dynamic Ice in Connections #11. The peers are always of two types: one is a human using a GUI client (written in Java) and the other is a robot (written in C++, using Ice-E). Here's the typical use case:
- Human turns the robot on, and the robot auto-connects to our relay server (very similar to the "registry" in Mark's article). Auto-connection includes login and registering a proxy to its servant with the relay.
- Human fires up the GUI client and logs in to the relay. Login includes registering a proxy to its servant with the relay.
- Human uses the GUI client to view the list of robots currently connected to the relay, selects one, and clicks "Connect" (which sends a connection request to the relay)
- Upon receipt of the connection request from the human, the relay sends a client proxy to the robot and a robot proxy to the GUI client (so they can make method calls on each other--the relay merely shuffles messages back and forth using Dynamic Ice as Mark described in the article).
- Once the connection is established (i.e. the client gets the robot's proxy), the client calls a startVideoStream() method on the robot. This causes the robot to start pushing JPEG images (obtained from its camera) to the client. Pushing is performed in a separate thread.
- The human can drive the robot by using the GUI client's interface. Commands are, at the moment, very simple (e.g. "drive forward", "tilt camera up", etc.). See the slice code below for more detail.
The problem we're having is that if the the human bangs away on the keyboard, sending, say, multiple drive commands in quick succession, the video stream it receives slows to a crawl (20 fps --> 1 fps or worse). The problem appears to result from the robot's use of the thread-per-connection model (it can't use thread pools since it's running under Ice-E). It seems as though thread-per-connection limits the robot to only doing one command at a time, in either direction (sending or receiving). Indeed, I've tested this using a fake robot (just a simple Java simulator, running under plain ol' Ice, not Ice-E) and switching to the thread pool model makes all video stream degradation disappear. Switching the Java robot simulator back to thread-per-connection results in the exact behavior we're witnessing with the real robot.
I read something in the Ice docs about possibly opening up another connection, and tried it in the robot simulator, but didn't notice any improvement. Maybe I didn't do it right though--all I did was have the robot code call ice_connectionId("video") on the client proxy it receives from the relay and use that new proxy solely for pushing the video frames. I was assuming video would use that new connection, while commands from the client to the robot would use the original connection that the robot made when it first connected to the relay (and registered its proxy, etc.)
Is that enough detail to ask for advice on how to fix things so that the robot can do send/receive more than one Ice command at a time?
Here's the (relevant) Slice code and all the config files we're using (yes, I know "GenericError" in the slice code is totally lame--it's just a placeholder until we better identify our exceptional conditions
):
Slice code for methods the robot and client use to register themselves with the relay (as well as some the relay uses to notify the client/robot of connections and disconnections):
Code:
#ifndef MRPL_PEER_ICE
#define MRPL_PEER_ICE
#include <Glacier2/Session.ice>
[["java:package:edu.cmu.ri.mrpl"]]
module peer
{
enum PeerAccessLevel {AccessLevelOwner,
AccessLevelOwnerRestricted,
AccessLevelNormalEnhanced,
AccessLevelNormal,
AccessLevelNormalRestricted,
AccessLevelGuestEnhanced,
AccessLevelGuest,
AccessLevelGuestRestricted,
AccessLevelNone};
struct PeerIdentifier
{
string userId;
string firstName;
string lastName;
};
exception PeerException
{
string reason;
};
exception PeerAccessException extends PeerException { };
exception PeerUnavailableException extends PeerException { };
exception PeerConnectionFailedException extends PeerException { };
exception DuplicateConnectionException extends PeerException { };
exception AuthenticationRequiredException extends PeerException { };
exception RegistrationException extends PeerException { };
interface UserConnectionEventHandler
{
["ami"] void forcedLogoutNotification();
};
interface PeerConnectionEventHandler
{
["ami"] void peerConnected(string peerUserId, PeerAccessLevel accessLevel, Object* peerProxy);
["ami"] void peerConnectedNoProxy(string peerUserId, PeerAccessLevel accessLevel);
["ami"] void peerDisconnected(string peerUserId);
};
interface ConnectionEventHandler extends UserConnectionEventHandler, PeerConnectionEventHandler
{
};
interface PeerRegistrationHandler
{
void registerCallbacks(Object* selfCallbackProxy, ConnectionEventHandler* connectionEventHandlerProxy) throws RegistrationException;
};
["java:type:java.util.HashSet<PeerIdentifier>"] sequence<PeerIdentifier> PeerIdentifierSet;
interface UserSession extends Glacier2::Session, PeerRegistrationHandler
{
PeerIdentifierSet getMyAvailablePeers() throws PeerException;
Object* connectToPeer(string peerUserId) throws PeerAccessException, PeerUnavailableException, PeerConnectionFailedException, DuplicateConnectionException, AuthenticationRequiredException;
PeerIdentifierSet getConnectedPeers() throws PeerException;
void disconnectFromPeer(string peerUserId);
void disconnectFromPeers();
};
};
#endif
Slice code common to the GUI client (a.k.a. TerkClient) and robot (a.k.a. Qwerk):
Code:
#ifndef TERK_PEER_COMMON_ICE
#define TERK_PEER_COMMON_ICE
#include <peer/MRPLPeer.ice>
[["java:package:edu.cmu.ri.mrpl"]]
module TeRK
{
enum ImageFormat {IMAGEJPEG, IMAGERGB24, IMAGERGB32, IMAGEGRAY8, IMAGEYUV420P, IMAGEUNKNOWN};
sequence<byte> ByteArray;
struct Image
{
int height;
int width;
int frameNum;
ImageFormat format;
ByteArray data;
};
exception GenericError
{
string reason;
};
interface VideoStreamerClient
{
int newFrame(Image frame) throws GenericError;
};
interface VideoStreamerServer
{
idempotent int startCamera() throws GenericError;
idempotent int stopCamera() throws GenericError;
idempotent int startVideoStream() throws GenericError;
idempotent int stopVideoStream() throws GenericError;
};
interface TerkClient extends peer::ConnectionEventHandler, VideoStreamerClient
{
};
interface Qwerk extends peer::ConnectionEventHandler, VideoStreamerServer
{
void cameraTiltUp();
void cameraTiltDown();
void cameraPanLeft();
void cameraPanRight();
void driveForward();
void driveBack();
void spinLeft();
void spinRight();
void stop();
};
};
#endif
The client's Ice config file (don't worry about the "@glacier.host@" stuff...the real, correct hostname gets inserted by our build tool depending on the build target):
Code:
Ice.ProgramName=DiffDriveTerkClient
Ice.Package.peer=edu.cmu.ri.mrpl
Ice.Package.TeRK=edu.cmu.ri.mrpl
Ice.Default.Package=edu.cmu.ri.mrpl
Ice.Default.Router=TerkGlacier/router:tcp -h @glacier.host@ -p 10004
Teleop.Client.Router=TerkGlacier/router:tcp -h @glacier.host@ -p 10004
Teleop.Client.Endpoints=
Ice.ACM.Client=0
Ice.ACM.Server=0
Ice.MonitorConnections=10
Ice.Warn.Connections=1
Ice.Logger.Timestamp=1
Ice.ThreadPool.Client.Size=5
Ice.ThreadPool.Client.SizeMax=20
Ice.ThreadPool.Server.Size=5
Ice.ThreadPool.Server.SizeMax=20
The robot's Ice config file:
Code:
Ice.ProgramName=RobotClient
Ice.Package.peer=edu.cmu.ri.mrpl
Ice.Package.TeRK=edu.cmu.ri.mrpl
Ice.Default.Package=edu.cmu.ri.mrpl
Ice.Default.Router=TerkGlacier/router:tcp -h @glacier.host@ -p 10004
Robot.Client.Router=TerkGlacier/router:tcp -h @glacier.host@ -p 10004
Robot.Client.Endpoints=
Ice.ACM.Client=0
Ice.ACM.Server=0
Ice.MonitorConnections=60
Ice.Warn.Connections=1
Ice.Trace.Network=0
Ice.Trace.Protocol=0
I've reached the forum post max length (sorry!), so I'll post the Glacier2 and relay config files as attachments.
Thanks heaps!
chris