public Response callAndWait( String recipientId, Map msg, int timeout )
            throws SampException {
        long finish = timeout > 0
                    ? System.currentTimeMillis() + timeout * 1000
                    : Long.MAX_VALUE;  // 3e8 years
        HubConnection connection = getConnection();
        String msgTag = createTag( this );
        responseMap_.put( msgTag, null );
        connection.call( recipientId, msgTag, msg );
        synchronized ( responseMap_ ) {
            while ( responseMap_.containsKey( msgTag ) &&
                    responseMap_.get( msgTag ) == null &&
                    System.currentTimeMillis() < finish ) {
                long millis = finish - System.currentTimeMillis();
                if ( millis > 0 ) {
                    try {
                        responseMap_.wait( millis );
                    }
                    catch ( InterruptedException e ) {
                        throw new SampException( "Wait interrupted", e );
                    }
                }
            }
            if ( responseMap_.containsKey( msgTag ) ) {
                Response response = (Response) responseMap_.remove( msgTag );
                if ( response != null ) {
                    return response;
                }
                else {
                    assert System.currentTimeMillis() >= finish;
                    throw new SampException( "Synchronous call timeout" );
                }
            }
            else {
                if ( connection != connection_ ) {
                    throw new SampException( "Hub connection lost" );
                }
                else {
                    throw new AssertionError();
                }
            }
        }
    }