Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.
Comment: Migrated to Confluence 5.3

This page describes the requirements and initial design of the new Qpid JMS Client that supports AMQP 1.0 (simply referred to as the "Qpid JMS Client" below).

Table of Contents

Requirements

Functional requirements

...

  • Project folder structure
  • Build tool configuration
  • CI server job
  • Automated HTML documentation generation.
Phase 1
  • Synchronous, Auto-Ack style 'simple client'
  • All Message types.
  • Main operations on JMS Connections, Sessions, Consumers, Producers, Queues, Message (Text?).
    • Include closing/stopping/deleting where applicable.
  • Configuration
    • Ability to configure settings such as pre-fetch etc
      • TODO per connection or per consumer or both? If the latter then not sure how to pass this setting in (address options?), given that JMS API doesn't offer an obvious way to do it.
      TODO define how JNDI configuration will work
  • Basic JNDI
  • SASL
  • Correct handling of non-happy path scenarios, namely:
    • Unexpected errors
    • Failures that are nevertheless AMQP-compliant, e.g. the Broker spontaneously sends a Close or End frame.
    • Timeouts due to connectivity failure etc. Necessary because of the asynchronous nature of AMQP.
    • Interruption of application-owned threads.

...

  • SSL
  • Transactions
  • Client Ack
  • SelectorsJNDI
  • MessageListener.onMessage()
  • Durable Subscriptions
  • JMS 2.0 features:
    • Asynchronous send
    • Shared Topic Subscriptions
  • Enhanced JNDI

 

Non-functional requirements

...

  • AMQP 0-x
  • ADDR and BURL addressing formats
  • Failover. Instead, this will be implemented in the future either entirely below or entirely above the client code.
  • Accepting legacy system properties (unelss they happen to match what we would choose now).

Design

Layers

No Format

+================================+
|
| JMS
|
| Implementations of javax.jms
|
+================================+
               |
               |
              \|/
+================================+
|
| AMQP
|
| Wrappers around Proton's engine
| classes, primarily to add locking
|
+================================+
            |                 |
            |                 |
           \|/                |
+======================+      |
|       Proton         |      |
|                      |      |
| Message |  Engine    |      |
|         |            |      |
+=========+============+      |
                    /|\       |
                     |        |
                     |       \|/
                   +====================
                   |
                   | JMS Driver interface
                   |
                   +=====================
                            /|\
                             |
                        +--------------+
                        |              |
                        |              |
                        |              |
                  +===========+        +==============
                  |                    |
                  |  Socket            |
                  |  driver            | Another driver,
                  |  (might use        | e.g. in-VM
                  |  Proton's Driver)  |
                  |                    |
                  +===========+        +==============
                    |
                    | TCP/IP
                    |
                   \|/
+================================+
|
| Broker
|
+================================+

...

  • Goals:
    • Minimise the number of locks
    • Define the correct locking order

Threads fall into two categories:

    • Document what application behaviour is legal (e.g. what can be done inside MessageListener.onMessage?)
    • Make the JMS client thread-safe when used legally.

Threads fall into three categories:

  1. Application threads using the JMS Application threads using the JMS API. Only use the top half of the Proton API.
  2. One or more driver threads for I/O. Managed by the JMS client. Only use the bottom half of the Proton API.
  3. Threads managed by the JMS client that call application code (e.g. MessageListener.onMessage, ExceptionListener.onException and CompletionListener.xxx).

For a given connection:

  1. State shared by multiple application threads (namely the state of the objects in the JMS layer) is guarded by the JMS ConnectionLock.
  2. State shared by the application and driver threads is guarded by the AmqpConnection lock. This shared state is:
    1. A small number of flags the application and driver threads use to indicate to each other that "something has changed".
    2. The Proton objects. This sharing occurs inside Proton but needs to be guarded by the JMS client because Proton itself is not thread-safe.

Synchronous operations must follow TODO extend the locking scheme to include the JMS Client-owned threads that call application code.

Synchronous operations must follow the locking scheme indicated by the following indicated by the following pseudo-code:

  • jmsObject.doStuff
    • obtain ConnectionLock
    • amqpObject.doAmqpStuff
      • obtain amqpConnection lock
      • protonObject.doProtonStuff
      • release amqpConnection lock
    • JmsConnection.stateChanged (event notification, e.g. to wake up the driver so that I/O occurs)
    • release ConnectionLock

...

  • Goals:
    • Consistent use of logging levels
    • TODO more...

TODO

Testing strategy

Goals:

  • Running all the tests should be fast (it should be exceptional for a test to take more than five seconds).
  • Tests operate at the correct level for their test type (see next section). Specifically:
    • Simple logic bugs cause unit tests to fail.
    • Errors in collaboration logic cause integration module tests to fail.
    • Most (if not all) of our integration tests should run in-process.
    • The official JMS 2 TCK
  • The following third-part tests will also be run against the client.
Integration tests

For each test, our main choice is which layer to designate as the boundary for our tests. Everything "below" this boundary will be replaced with a mock implementation ("mock" is defined in a broad sense - it does not assume a imply the use of a mocking framework).

Our options are listed below in increasing system boundary extent. As the extent increases, the usual trade-offs apply:

  • More layers of the real JMS client will be exercised, thereby increasing the confidence that our tests give us.
  • We get less control over the tests, both in terms of their inputs and the assertions we can test.
  • Tests become harder to debug.

...

Layer to mock

...

Comments

...

Proton

...

The broker (out-of-process).
We would either write a simple broker or use the existing one.
The client test would more closely resemble a conventional application than a JUnit test.

...

Useful for testing multi-threaded behaviour

  • :
    • Official JMS 2 TCK.
    • Joram.
Test types

We will write a number of each of the following tests.

  • Unit test: class that attempts to test an individual class. Some unit tests will incidentally tests several other closely related classes too.
  • Module test: a class that attempts to test the full JMS client, but using a stub/mock etc instead of a broker. In most cases, we want to test that:
    • Calling specific JMS methods causes the correct AMQP to be sent, and
    • The client correctly handles AMQP sent by its peer.
  • System test: a class that tests the behaviour of the JMS client when communicating with a real peer such as the Qpid Broker. Assertions will be written mostly in terms of the JMS API. We may re-use some of the existing Qpid system tests.
Module tests

The following diagram shows how module tests will work:

No Format
+================================+
|
| JUnit test
|
| 1. Create an in-process TestAmqpPeer
| 2. Set up TestAmqpPeer behaviour and expectations
| 3. Call some JMS methods
| 
+================================+
   |       |             |
   |       |             |
   |       |            \|/
   |       |    +================================+
   |       |    |
   |       |    | JMS client
   |       |    |
   |       |    +================================+
   |       |                |                 |
   |    latch/              |                 |
   |     unlatch etc        |                 |
   |       |               \|/                |
   |       |    +======================+      |
   |       |    |       Proton         |      |
expect/    |    |                      |      |
 assert    |    | Message |  Engine    |      |
   |       |    |         |            |      |
   |       |    +=========+============+      |
   |       |                /|\               |
   |       |                 |                |
   |      \|/               \|/              \|/
   |    +=======================================
   |    |
   |    | In-process Driver
   |    |
   |    +=======================================
   |                      /|\
   |                       |
   |                       | Bytes
   |                       |
  \|/                     \|/
+===================================+
|
| TestAmqpPeer
|
+===================================+
In-process Driver implementation
  • Should use a threading model that is as similar as possible to the TCP/IP driver, to make the test as realistic as possible.
  • Should expose methods to give the test a way to control, for a given connection, the relative order of (1) events in the "application" thread (typically the main test thread) and (2) events in the driver thread. This should reduce the number of race condition bugs in our code. We will probably implement this control using techniques such as:
    • Latches, CyclicBarriers etc.
    • Controlling the chunking of the byte production/consumption by both the In-process Driver and the TestAmqpPeer.
TestAmqpPeer implementation

Each test will give behaviour to a TestAmqpPeer. This behaviour will mostly be expressed in terms of AMQP frames. Here are prose examples (we don't yet know how these will be expressed in code)

Expected frame

Frame to respond with

An Open frame with container-id "xyz"

The following canned Open frame: ...

Expected frame

Frame to respond with

An Open frame with container-id "xyz"

The following sequence of frames representing a refused connection

Expected frame

Frame to respond with

Any Open

Canned Open frame: ...

Any Begin

Canned Begin frame: ...

An Attach matching the following criteria: ...

The following Attach: ...

In order to increase our confidence in the AMQP interoperabilty of the JMS client, we want to avoid using the same Proton stack in the test peer as in the client. Therefore, Decoding and encoding will be done using proton-api's Data class. The AmqpTestPeer will minimise its use of other Proton classes.

SASL

Most tests will perform minimal SASL negotiation, simulating simple, successful SASL authentication. We will write specific SASL tests to exercise more complex scenarios

We expect all of the above mocking options to be used at some point. Initially we will favour mocking the broker in-process where possible. This is because:

...

.