Esempio n. 1
0
  /** Used to passthrough the ping of the relevant server */
  @EventHandler
  public void onPing(ProxyPingEvent event) {
    if (!PodConfig.inst().getPingPassthrough()) return;

    int protocol = event.getConnection().getVersion();
    ServerInfo info = getServer(protocol);
    if (info == null) return;

    final Exchanger<ServerPing> exch = new Exchanger<>();

    // Get the child server's ping, pass it to back to main ping.
    info.ping(
        new Callback<ServerPing>() {
          @Override
          public void done(ServerPing result, Throwable error) {
            try {
              exch.exchange(result);
            } catch (InterruptedException e) {
              e.printStackTrace();
            }
          }
        });

    try {
      ServerPing ping = exch.exchange(null); // Wait for child ping thread to finish
      if (ping == null) return;
      event.setResponse(ping);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
  }
  @Test
  public void testSampleLevel() throws Exception {
    final String name = "MySamples";
    ProfileCollector collector = getCollector(serverNode);
    ProfileConsumer cons1 = collector.getConsumer("cons1");
    final ProfileSample sample = cons1.registerSampleSource(name, true, -1, ProfileLevel.MAX);

    // Because the listener is running in a different thread, JUnit
    // is not able to report the assertions and failures.
    // Use an exchanger to synchronize between the threads and communicate
    // any problems.
    final Exchanger<AssertionError> errorExchanger = new Exchanger<AssertionError>();

    final List<Long> testValues = new ArrayList<Long>();
    testValues.add(101L);
    testValues.add(-22L);
    // The owner for our positive test.  The listener uses this owner
    // to find the ProfileReport for the task in this test.
    final Identity positiveOwner = new DummyIdentity("samplelevel");
    TestListener test =
        new TestListener(
            new TestSampleReport(
                name, positiveOwner,
                errorExchanger, testValues));
    profileCollector.addListener(test, true);
    txnScheduler.runTask(
        new TestAbstractKernelRunnable() {
          public void run() {
            // The default profile level is MIN so we don't expect
            // to see the samples.
            for (Long v : testValues) {
              sample.addSample(v);
            }
          }
        },
        taskOwner);

    AssertionError error = errorExchanger.exchange(null, 100, TimeUnit.MILLISECONDS);
    if (error != null) {
      throw new AssertionError(error);
    }

    cons1.setProfileLevel(ProfileLevel.MAX);
    txnScheduler.runTask(
        new TestAbstractKernelRunnable() {
          public void run() {
            // Because we bumped the consumer's profile level,
            // we expect the samples to appear
            for (Long v : testValues) {
              sample.addSample(v);
            }
          }
        },
        positiveOwner);

    error = errorExchanger.exchange(null, 100, TimeUnit.MILLISECONDS);
    if (error != null) {
      throw new AssertionError(error);
    }
  }
  @Test
  public void testCounterLevel() throws Exception {
    final String name = "MyCounter";
    ProfileCollector collector = getCollector(serverNode);
    ProfileConsumer cons1 = collector.getConsumer("c1");
    // Register a counter to be updated only at the max level
    final ProfileCounter counter = cons1.registerCounter(name, true, ProfileLevel.MAX);

    // Because the listener is running in a different thread, JUnit
    // is not able to report the assertions and failures.
    // Use an exchanger to synchronize between the threads and communicate
    // any problems.
    final Exchanger<AssertionError> errorExchanger = new Exchanger<AssertionError>();

    // The owner for our positive test.  The listener uses this owner
    // to find the ProfileReport for the task in this test.
    final Identity positiveOwner = new DummyIdentity("counterlevel");
    TestListener test =
        new TestListener(new TestCounterReport(name, positiveOwner, errorExchanger, 1));
    profileCollector.addListener(test, true);

    txnScheduler.runTask(
        new TestAbstractKernelRunnable() {
          public void run() {
            // The default profile level is MIN so we don't expect
            // to see the counter incremented.
            counter.incrementCount();
          }
        },
        taskOwner);

    AssertionError error = errorExchanger.exchange(null, 100, TimeUnit.MILLISECONDS);
    if (error != null) {
      throw new AssertionError(error);
    }

    cons1.setProfileLevel(ProfileLevel.MAX);
    txnScheduler.runTask(
        new TestAbstractKernelRunnable() {
          public void run() {
            // Because we bumped the consumer's profile level,
            // we expect the counter
            counter.incrementCount();
          }
        },
        positiveOwner);

    error = errorExchanger.exchange(null, 100, TimeUnit.MILLISECONDS);
    if (error != null) {
      throw new AssertionError(error);
    }
  }
  @Test
  public void testCounter() throws Exception {
    final String name = "counter";
    ProfileCollector collector = getCollector(serverNode);
    ProfileConsumer cons1 = collector.getConsumer("c1");
    // Register a counter to be noted at all profiling levels
    final ProfileCounter counter = cons1.registerCounter(name, true, ProfileLevel.MIN);

    // Because the listener is running in a different thread, JUnit
    // is not able to report the assertions and failures.
    // Use an exchanger to synchronize between the threads and communicate
    // any problems.
    final Exchanger<AssertionError> errorExchanger = new Exchanger<AssertionError>();

    // The owner for our positive test.  The listener uses this owner
    // to find the ProfileReport for the task in this test.
    final Identity positiveOwner = new DummyIdentity("owner");
    TestListener test =
        new TestListener(new TestCounterReport(name, positiveOwner, errorExchanger, 1));
    profileCollector.addListener(test, true);

    // We run with the myOwner because we expect to see the
    // value in the test report.
    txnScheduler.runTask(
        new TestAbstractKernelRunnable() {
          public void run() {
            counter.incrementCount();
          }
        },
        positiveOwner);

    AssertionError error = errorExchanger.exchange(null, 100, TimeUnit.MILLISECONDS);
    if (error != null) {
      // Rethrow with the original error as the cause so we see
      // both stack traces.
      throw new AssertionError(error);
    }

    txnScheduler.runTask(
        new TestAbstractKernelRunnable() {
          public void run() {}
        },
        taskOwner);

    error = errorExchanger.exchange(null, 100, TimeUnit.MILLISECONDS);
    if (error != null) {
      throw new AssertionError(error);
    }
  }
    public void run() {
      AssertionError error = null;
      ProfileReport report = TestListener.report;

      // Check to see if we expected the counter value to be
      // updated in this report.
      boolean update = report.getTaskOwner().equals(positiveOwner);
      if (update) {
        try {
          // Find the counter, make sure it was incremented
          Long value = report.getUpdatedTaskCounters().get(name);
          System.err.println("got counter value of " + value);
          assertEquals(incrementValue, value.intValue());
        } catch (AssertionError e) {
          error = e;
        }
      } else {
        try {
          Long value = report.getUpdatedTaskCounters().get(name);
          assertNull("expected no value", value);
        } catch (AssertionError e) {
          error = e;
        }
      }
      // Signal that we're done, and return the exception
      try {
        errorExchanger.exchange(error);
      } catch (InterruptedException ignored) {
        // do nothing
      }
    }
    public void run() {
      AssertionError error = null;
      ProfileReport report = TestListener.report;
      // Check to see if we expected the sample values to be
      // updated in this report.
      boolean update = report.getTaskOwner().equals(positiveOwner);
      List<Long> values = report.getUpdatedTaskSamples().get(name);

      try {
        if (update) {
          assertEquals(expectedValues.size(), values.size());
          for (int i = 0; i < expectedValues.size(); i++) {
            Long found = values.get(i);
            System.err.println("found value: " + found);
            assertEquals(expectedValues.get(i), found);
          }
        } else {
          assertNull(values);
        }
      } catch (AssertionError e) {
        error = e;
      }

      // Signal that we're done, and return the exception
      try {
        errorExchanger.exchange(error);
      } catch (InterruptedException ignored) {
        // do nothing
      }
    }
  @Test
  public void testOperationMediumToMaxLevel() throws Exception {
    ProfileCollector collector = getCollector(serverNode);
    ProfileConsumer cons1 = collector.getConsumer("c1");
    final ProfileOperation op = cons1.registerOperation("something", ProfileLevel.MEDIUM);

    // Because the listener is running in a different thread, JUnit
    // is not able to report the assertions and failures.
    // Use an exchanger to synchronize between the threads and communicate
    // any problems.
    final Exchanger<AssertionError> errorExchanger = new Exchanger<AssertionError>();

    // The owner for our positive test.  The listener uses this owner
    // to find the ProfileReport for the task in this test.
    final Identity positiveOwner = new DummyIdentity("opmedtomax");
    TestListener test =
        new TestListener(new TestOperationReport(op, positiveOwner, errorExchanger));
    profileCollector.addListener(test, true);

    txnScheduler.runTask(
        new TestAbstractKernelRunnable() {
          public void run() {
            // We do not expect to see this reported.
            op.report();
          }
        },
        taskOwner);

    AssertionError error = errorExchanger.exchange(null, 100, TimeUnit.MILLISECONDS);
    if (error != null) {
      throw new AssertionError(error);
    }

    cons1.setProfileLevel(ProfileLevel.MAX);
    txnScheduler.runTask(
        new TestAbstractKernelRunnable() {
          public void run() {
            op.report();
          }
        },
        positiveOwner);

    error = errorExchanger.exchange(null, 100, TimeUnit.MILLISECONDS);
    if (error != null) {
      throw new AssertionError(error);
    }
  }
Esempio n. 8
0
 @Override
 public void bucketLoaded(Bucket<DummyEvent> bucket) {
   try {
     super.bucketLoaded(bucket);
     eventBucketExchanger.exchange(bucket.bucketKey);
   } catch (InterruptedException e) {
     throw new RuntimeException(e);
   }
 }
Esempio n. 9
0
 public synchronized void start() throws IOException {
   if (worker != null) {
     throw new IllegalStateException("FileSystemWatcher already started."); // NOI18N
   }
   worker = Executors.newSingleThreadExecutor(new DaemonThreadFactory());
   final Exchanger<Object> exchanger = new Exchanger<Object>();
   worker.execute(
       new Runnable() {
         public @Override void run() {
           try {
             Pointer[] _rtData = null;
             try {
               _rtData = createFSEventStream();
             } catch (Throwable ex) {
               exchanger.exchange(ex);
             } finally {
               if (_rtData != null) {
                 exchanger.exchange(_rtData);
                 cf.CFRunLoopRun();
               }
             }
           } catch (InterruptedException ie) {
             LOG.log(Level.WARNING, "Watcher interruped during start", ie); // NOI18N
           }
         }
       });
   final Object _data;
   try {
     _data = exchanger.exchange(null);
   } catch (InterruptedException ex) {
     throw (InterruptedIOException) new InterruptedIOException().initCause(ex);
   }
   assert _data != null;
   if (_data instanceof Throwable) {
     worker.shutdown();
     worker = null;
     throw new IOException((Throwable) _data);
   } else {
     rtData = (Pointer[]) _data;
   }
 }
Esempio n. 10
0
 private void testRound(List<DummyEvent> events) {
   for (DummyEvent event : events) {
     deduper.input.process(event);
   }
   try {
     eventBucketExchanger.exchange(null, 1, TimeUnit.SECONDS);
   } catch (InterruptedException e) {
     throw new RuntimeException(e);
   } catch (TimeoutException e) {
     logger.debug("Timeout Happened");
   }
 }
Esempio n. 11
0
 public int read(byte[] dst, int estimated) {
   if (mReadBuffer.position() == mAvailable) {
     try {
       mReadBuffer = mExchanger.exchange(mReadBuffer);
     } catch (InterruptedException e) {
       return 0;
     }
   }
   int read = Math.min(estimated, mAvailable - mReadBuffer.position());
   mReadBuffer.get(dst, 0, read);
   mAvailable -= read;
   return read;
 }
Esempio n. 12
0
  @Test
  public void testDeduperRedeploy() throws Exception {
    com.datatorrent.api.Attribute.AttributeMap.DefaultAttributeMap attributes =
        new com.datatorrent.api.Attribute.AttributeMap.DefaultAttributeMap();
    attributes.put(DAG.APPLICATION_ID, APP_ID);
    attributes.put(DAG.APPLICATION_PATH, applicationPath);

    deduper.addEventManuallyToWaiting(new DummyEvent(100, System.currentTimeMillis()));
    deduper.setup(new OperatorContextTestHelper.TestIdOperatorContext(0, attributes));
    eventBucketExchanger.exchange(null, 500, TimeUnit.MILLISECONDS);
    deduper.endWindow();
    deduper.teardown();
  }
Esempio n. 13
0
  @Override
  public void run() {
    try {
      // 交換前のデータを表示
      System.out.println(getName() + "(交換前): " + this.data);

      // コンストラクタで指定されたミリ秒待機
      Thread.sleep(this.time);

      // データを交換
      data = exchanger.exchange(this.data);

      // 交換後のデータを表示
      System.out.println(getName() + "(交換後): " + this.data);

    } catch (InterruptedException e) {
      e.printStackTrace();
    }
  }
    public void run() {
      AssertionError error = null;
      ProfileReport report = TestListener.report;
      // Check to see if we expected the operation to be in this report.
      boolean update = report.getTaskOwner().equals(positiveOwner);
      boolean found = report.getReportedOperations().contains(operation);
      try {
        assertEquals(update, found);
      } catch (AssertionError e) {
        error = e;
      }

      // Signal that we're done, and return the exception
      try {
        errorExchanger.exchange(error);
      } catch (InterruptedException ignored) {
        // do nothing
      }
    }
Esempio n. 15
0
 /**
  * Fault response case
  *
  * @throws Exception
  */
 public void testNonAnonymousFaultTo2() throws Exception {
   invokeAsync(
       createDispatchWithoutAddressing(),
       TestMessages.NON_ANONYMOUS_FAULT_TO_COMPLETE_FAULTY_MESSAGE,
       S11_NS,
       nonAnonAddress,
       action,
       endpointAddress,
       "testNonAnonymousReplyTo");
   // Lets see we get a response in 60 s
   SOAPMessage m =
       respMsgExchanger.exchange(null, TestMessages.CLIENT_MAX_TIMEOUT, TimeUnit.SECONDS);
   // System.out.println("****************************");
   // m.writeTo(System.out);
   // System.out.println("\n****************************");
   SOAPBody sb = m.getSOAPBody();
   assertTrue(sb.hasFault());
   SOAPFault fault = sb.getFault();
   assertEquals(fault.getFaultString(), "Negative numbers can't be added!");
 }
Esempio n. 16
0
 public void fillBuffer() {
   try {
     while (mWriteBuffer.remaining() > 0 && mChannel.read(mWriteBuffer) > 0) ;
   } catch (IOException e) {
     throw new Error(e);
   }
   mAvailable += mWriteBuffer.position();
   mWriteBuffer.flip();
   if (mFirstRun) {
     mFirstRun = false;
     ByteBuffer tmp = mReadBuffer;
     mReadBuffer = mWriteBuffer;
     mWriteBuffer = tmp;
   } else {
     try {
       mWriteBuffer = mExchanger.exchange(mWriteBuffer);
     } catch (InterruptedException e) {
       throw new Error(e);
     }
   }
 }
Esempio n. 17
0
  public void testNonAnonymousReplyTo() throws Exception {
    invokeAsync(
        createDispatchWithoutAddressing(),
        TestMessages.NON_ANONYMOUS_REPLY_TO_COMPLETE_MESSAGE,
        S11_NS,
        nonAnonAddress,
        action,
        endpointAddress,
        "testNonAnonymousReplyTo");

    // Lets see we get a response in 60 s
    SOAPMessage m =
        respMsgExchanger.exchange(null, TestMessages.CLIENT_MAX_TIMEOUT, TimeUnit.SECONDS);
    // System.out.println("****************************");
    // m.writeTo(System.out);
    // System.out.println("\n****************************");
    SOAPBody sb = m.getSOAPBody();
    Iterator itr =
        sb.getChildElements(
            new QName("http://server.responses.wsa.fromjava/", "addNumbersResponse"));
    assertTrue(itr.hasNext());
  }
  @Test
  public void testMaxIdleWithRequest11NoClientClose() throws Exception {
    final Exchanger<EndPoint> exchanger = new Exchanger<>();
    configureServer(
        new EchoHandler() {
          @Override
          public void handle(
              String target,
              Request baseRequest,
              HttpServletRequest request,
              HttpServletResponse response)
              throws IOException, ServletException {
            try {
              exchanger.exchange(baseRequest.getHttpChannel().getEndPoint());
            } catch (Exception e) {
              e.printStackTrace();
            }
            super.handle(target, baseRequest, request, response);
          }
        });
    Socket client = newSocket(_serverURI.getHost(), _serverURI.getPort());
    client.setSoTimeout(10000);

    assertFalse(client.isClosed());

    OutputStream os = client.getOutputStream();
    InputStream is = client.getInputStream();

    String content = "Wibble";
    byte[] contentB = content.getBytes("utf-8");
    os.write(
        ("POST /echo HTTP/1.1\r\n"
                + "host: "
                + _serverURI.getHost()
                + ":"
                + _serverURI.getPort()
                + "\r\n"
                + "content-type: text/plain; charset=utf-8\r\n"
                + "content-length: "
                + contentB.length
                + "\r\n"
                + "connection: close\r\n"
                + "\r\n")
            .getBytes("utf-8"));
    os.write(contentB);
    os.flush();

    // Get the server side endpoint
    EndPoint endPoint = exchanger.exchange(null, 10, TimeUnit.SECONDS);

    // read the response
    IO.toString(is);

    // check client reads EOF
    assertEquals(-1, is.read());

    TimeUnit.MILLISECONDS.sleep(3 * MAX_IDLE_TIME);

    // further writes will get broken pipe or similar
    try {
      for (int i = 0; i < 1000; i++) {
        os.write(
            ("GET / HTTP/1.0\r\n"
                    + "host: "
                    + _serverURI.getHost()
                    + ":"
                    + _serverURI.getPort()
                    + "\r\n"
                    + "connection: keep-alive\r\n"
                    + "\r\n")
                .getBytes("utf-8"));
        os.flush();
      }
      Assert.fail("half close should have timed out");
    } catch (SocketException e) {
      // expected
    }

    // check the server side is closed
    Assert.assertFalse(endPoint.isOpen());
  }
  @Test
  public void testMaxIdleWithRequest10ClientIgnoresClose() throws Exception {
    final Exchanger<EndPoint> exchanger = new Exchanger<>();
    configureServer(
        new HelloWorldHandler() {
          @Override
          public void handle(
              String target,
              Request baseRequest,
              HttpServletRequest request,
              HttpServletResponse response)
              throws IOException, ServletException {
            try {
              exchanger.exchange(baseRequest.getHttpChannel().getEndPoint());
            } catch (Exception e) {
              e.printStackTrace();
            }
            super.handle(target, baseRequest, request, response);
          }
        });
    Socket client = newSocket(_serverURI.getHost(), _serverURI.getPort());
    client.setSoTimeout(10000);

    assertFalse(client.isClosed());

    OutputStream os = client.getOutputStream();
    InputStream is = client.getInputStream();

    os.write(
        ("GET / HTTP/1.0\r\n"
                + "host: "
                + _serverURI.getHost()
                + ":"
                + _serverURI.getPort()
                + "\r\n"
                + "connection: close\r\n"
                + "\r\n")
            .getBytes("utf-8"));
    os.flush();

    // Get the server side endpoint
    EndPoint endPoint = exchanger.exchange(null, 10, TimeUnit.SECONDS);
    if (endPoint instanceof SslConnection.DecryptedEndPoint)
      endPoint = endPoint.getConnection().getEndPoint();

    // read the response
    String result = IO.toString(is);
    Assert.assertThat("OK", result, containsString("200 OK"));

    // check client reads EOF
    assertEquals(-1, is.read());
    assertTrue(endPoint.isOutputShutdown());

    Thread.sleep(2 * MAX_IDLE_TIME);

    // further writes will get broken pipe or similar
    try {
      long end = System.currentTimeMillis() + MAX_IDLE_TIME + 3000;
      while (System.currentTimeMillis() < end) {
        os.write("THIS DATA SHOULD NOT BE PARSED!\n\n".getBytes("utf-8"));
        os.flush();
        Thread.sleep(100);
      }
      Assert.fail("half close should have timed out");
    } catch (SocketException e) {
      // expected

      // Give the SSL onClose time to act
      Thread.sleep(100);
    }

    // check the server side is closed
    Assert.assertFalse(endPoint.isOpen());
  }
  @Test
  public void testOperationMultiple() throws Exception {
    ProfileCollector collector = getCollector(serverNode);
    ProfileConsumer cons1 = collector.getConsumer("c1");
    final ProfileOperation op = cons1.registerOperation("something", ProfileLevel.MIN);
    final ProfileOperation op1 = cons1.registerOperation("else", ProfileLevel.MIN);

    // Because the listener is running in a different thread, JUnit
    // is not able to report the assertions and failures.
    // Use an exchanger to synchronize between the threads and communicate
    // any problems.
    final Exchanger<AssertionError> errorExchanger = new Exchanger<AssertionError>();

    final Identity myOwner = new DummyIdentity("me");
    TestListener test =
        new TestListener(
            new Runnable() {
              public void run() {
                AssertionError error = null;
                ProfileReport report = TestListener.report;
                if (report.getTaskOwner().equals(myOwner)) {
                  try {
                    List<ProfileOperation> ops = TestListener.report.getReportedOperations();
                    for (ProfileOperation po : ops) {
                      System.err.println(po);
                    }
                    int opIndex1 = ops.indexOf(op);
                    int opIndex2 = ops.lastIndexOf(op);
                    int op1Index1 = ops.indexOf(op1);
                    int op1Index2 = ops.lastIndexOf(op1);

                    // We expect to see op twice, and op1 once
                    assertTrue(opIndex1 != -1);
                    assertTrue(opIndex2 != -1);
                    assertTrue(op1Index1 != -1);
                    assertTrue(op1Index2 == op1Index1);

                    // We expect the op ordering to be maintained
                    assertTrue(opIndex1 < op1Index1);
                    assertTrue(op1Index1 < opIndex2);
                  } catch (AssertionError e) {
                    error = e;
                  }
                }

                // Signal that we're done, and return the exception
                try {
                  errorExchanger.exchange(error);
                } catch (InterruptedException ignored) {
                  // do nothing
                }
              }
            });
    profileCollector.addListener(test, true);

    txnScheduler.runTask(
        new TestAbstractKernelRunnable() {
          public void run() {
            // We expect to see the operation in the profile report
            op.report();
            op1.report();
            op.report();
          }
        },
        myOwner);

    AssertionError error = errorExchanger.exchange(null, 100, TimeUnit.MILLISECONDS);
    if (error != null) {
      throw new AssertionError(error);
    }
  }
  @Test
  @InSequence(3)
  public void callCountedMethodOnce() throws InterruptedException, TimeoutException {
    assertThat("Counter is not registered correctly", registry.getCounters(), hasKey(COUNTER_NAME));
    Counter counter = registry.getCounters().get(COUNTER_NAME);

    // Call the counted method, block and assert it's been counted
    final Exchanger<Long> exchanger = new Exchanger<>();
    Thread thread =
        new Thread(
            new Runnable() {
              @Override
              public void run() {
                try {
                  exchanger.exchange(
                      bean.countedMethod(
                          new Callable<Long>() {
                            @Override
                            public Long call() throws Exception {
                              exchanger.exchange(0L);
                              return exchanger.exchange(0L);
                            }
                          }));
                } catch (InterruptedException cause) {
                  throw new RuntimeException(cause);
                }
              }
            });
    final AtomicInteger uncaught = new AtomicInteger();
    thread.setUncaughtExceptionHandler(
        new Thread.UncaughtExceptionHandler() {
          @Override
          public void uncaughtException(Thread t, Throwable e) {
            uncaught.incrementAndGet();
          }
        });
    thread.start();

    // Wait until the method is executing and make sure that the counter has been incremented
    exchanger.exchange(0L, 5L, TimeUnit.SECONDS);
    assertThat(
        "Counter count is incorrect",
        counter.getCount(),
        is(equalTo(COUNTER_COUNT.incrementAndGet())));

    // Exchange the result and unblock the method execution
    Long random = 1 + Math.round(Math.random() * (Long.MAX_VALUE - 1));
    exchanger.exchange(random, 5L, TimeUnit.SECONDS);

    // Wait until the method has returned
    assertThat(
        "Counted method return value is incorrect", exchanger.exchange(0L), is(equalTo(random)));

    // Then make sure that the counter has been decremented
    assertThat(
        "Counter count is incorrect",
        counter.getCount(),
        is(equalTo(COUNTER_COUNT.decrementAndGet())));

    // Finally make sure calling thread is returns correctly
    thread.join();
    assertThat("Exception thrown in method call thread", uncaught.get(), is(equalTo(0)));
  }
Esempio n. 22
0
  /**
   * TODO time out is actually double as it waits for the producer and then waits for the response.
   * Use an atomic long to manage the countdown
   */
  @Override
  public void sendMessage(
      final Exchange exchange,
      final AsyncCallback callback,
      final MessageProducerResources producer,
      final ReleaseProducerCallback releaseProducerCallback)
      throws Exception {
    Message request = getEndpoint().getBinding().makeJmsMessage(exchange, producer.getSession());

    String correlationId =
        exchange.getIn().getHeader(JmsConstants.JMS_CORRELATION_ID, String.class);
    if (correlationId == null) {
      // we append the 'Camel-' prefix to know it was generated by us
      correlationId = GENERATED_CORRELATION_ID_PREFIX + getUuidGenerator().generateUuid();
    }

    Object responseObject = null;
    Exchanger<Object> messageExchanger = new Exchanger<Object>();
    JmsMessageHelper.setCorrelationId(request, correlationId);
    EXCHANGERS.put(correlationId, messageExchanger);

    MessageConsumerResources consumer = consumers.borrowObject();
    JmsMessageHelper.setJMSReplyTo(request, consumer.getReplyToDestination());
    consumers.returnObject(consumer);
    producer.getMessageProducer().send(request);

    // Return the producer to the pool so another waiting producer
    // can move forward
    // without waiting on us to complete the exchange
    try {
      releaseProducerCallback.release(producer);
    } catch (Exception exception) {
      // thrown if the pool is full. safe to ignore.
    }

    try {
      responseObject = messageExchanger.exchange(null, getResponseTimeOut(), TimeUnit.MILLISECONDS);
      EXCHANGERS.remove(correlationId);
    } catch (InterruptedException e) {
      log.debug("Exchanger was interrupted while waiting on response", e);
      exchange.setException(e);
    } catch (TimeoutException e) {
      log.debug("Exchanger timed out while waiting on response", e);
      exchange.setException(e);
    }

    if (exchange.getException() == null) {
      if (responseObject instanceof Throwable) {
        exchange.setException((Throwable) responseObject);
      } else if (responseObject instanceof Message) {
        Message message = (Message) responseObject;

        SjmsMessage response =
            new SjmsMessage(message, consumer.getSession(), getEndpoint().getBinding());
        // the JmsBinding is designed to be "pull-based": it will populate the Camel message on
        // demand
        // therefore, we link Exchange and OUT message before continuing, so that the JmsBinding has
        // full access
        // to everything it may need, and can populate headers, properties, etc. accordingly (solves
        // CAMEL-6218).
        exchange.setOut(response);
      } else {
        exchange.setException(new CamelException("Unknown response type: " + responseObject));
      }
    }

    callback.done(isSynchronous());
  }