Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

JMS producer multi session #1148

Merged
merged 12 commits into from
Aug 31, 2018

Conversation

andreas-schroeder
Copy link
Contributor

  • JmsProducerStage now supports multiple sessions by behaving
    similarly to MapAsync
  • Producers are pooled and parallelism is controlled by how producers
    are returned to the pool.
  • Created a separate JmsMessageProducer class to keep the stage logic
    more focussed on coordinating elements within the stage.
  • Grouped the logic for creating the execution context to use in the
    producer stage as well as in the consumer stage.

Fixes #1144

@ennru ennru added the p:jms label Aug 15, 2018
case v: Byte => message.setByteProperty(key, v)
case v: Short => message.setShortProperty(key, v)
case v: Long => message.setLongProperty(key, v)
case v: Double => message.setDoubleProperty(key, v)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the properties contain a value which is not of any of these types, would it make sense to explicitly throw an exception instead of the default MatchError?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In general, I would say yes.

However, the Jms producer crashes on any exception, and does not distinguish between retriable errors and non-retriable ones - it doesn't perform any retries. So in the end, the behavior would still be very similar if we introduced a custom exception here. It would make much more sense to introduce a custom exception if the Jms producer stage did retries, and would not try to do those retries if the property value type is of an unexpected type.

(please note: this is part of the code I moved out of the stage into a separate class - I didn't modify it)

Copy link
Contributor Author

@andreas-schroeder andreas-schroeder Aug 22, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@WellingR after giving it some thought, those exceptions can be handy in supervision logic too, so here is a proposal - what do you think? a280576

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks better!
I had some comments about the exception traits though.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 I've followed your suggestions :)

@ennru
Copy link
Member

ennru commented Aug 20, 2018

Test compilation fails for Scala 2.11.

@andreas-schroeder
Copy link
Contributor Author

@ennru thanks for notifying and sorry for not seeing this myself. I will take care of fixing the issues.

@andreas-schroeder
Copy link
Contributor Author

@ennru compilation is now fixed, I just happen to add exception classes as suggested by @WellingR .

Copy link
Member

@ennru ennru left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great work!
I think your solution with the Holder and its callbacks deserves a description in comments so that others can understand it more easily.

@@ -40,11 +40,23 @@ private[jms] trait JmsConnector { this: GraphStageLogic =>
onSessionOpened(session)
}

private[jms] def initSessionAsync(dispatcher: Dispatcher): Future[Unit] = {
ec = materializer match {
private[jms] def executionContext(attributes: Attributes): ExecutionContext = {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This and a few other things in this class should be just regular private or protected.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

agreed; I wanted to follow the style of the existing implementation, I'll change that to simpler visibility restrictions.


private val jmsProducers: Buffer[JmsMessageProducer] = Buffer(settings.sessionCount, settings.sessionCount)

private val buffer: Buffer[Holder[A]] = Buffer(settings.sessionCount, settings.sessionCount)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would producerWithMessage describe better what it holds?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how about inFlightMessagesWithProducer?

@@ -782,5 +785,92 @@ class JmsConnectorsSpec extends JmsSpec {

result.futureValue should ===(input)
}

"publish and consume strings through a queue with multiple sessions" in withServer() { ctx =>
//#connection-factory
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You need to remove the copied //# as that marks what Paradox cuts out for the documentation.
It would be very useful if you added a section on the producer settings in the documentation.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see, I wasn't sure what //# does. I'll add producer settings documentation as well.

/**
* Marker trait indicating that the exception thrown is intermittent. The failed operation might succeed if tried again.
*/
trait RetriableException extends Exception
Copy link
Member

@WellingR WellingR Aug 25, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It makes some sense to mark some exception cases as NonRetriableException. However the other way around RetriableException does not make as much sense.

The essence is that you want to point out for some specific exceptions that a retry makes no sense. However for all other exceptions, a retry might make sense (so there is no real need for the marker trait RetriableException especially since the code does not even use this).

/**
* Marker trait indicating that the exception thrown is persistent. The operation will always fail when retried.
*/
trait NonRetriableException extends Exception
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this is a jms library, maybe we should call this a NonRetriableJmsException.

@andreas-schroeder
Copy link
Contributor Author

Travis failed because an AMQP test failed: https://travis-ci.org/akka/alpakka/jobs/420470777#L680

number of parallel sessions increases throughput at the cost of message ordering. While the messages may arrive
out of order at the Jms broker, the producer flow outputs messages in the order they are received.
* `timeToLive` (optional) the time messages should be kept on the Jms broker. This setting can be overridden on
individual messages. If not set, messages will never expire.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that I didn't document acknowledgeMode. This is because I read various threads on Jms stating that this setting is not considered when producing records.

@andreas-schroeder
Copy link
Contributor Author

Looks like it is always the same AMQP test that is consistently failing:
https://travis-ci.org/akka/alpakka/jobs/420947066#L631
https://travis-ci.org/akka/alpakka/jobs/420780309#L810
Should I rebase this PR on top of master?

@ennru
Copy link
Member

ennru commented Aug 27, 2018

Don't worry for the AMQP test, it is not your fault. See #1167.

@ennru
Copy link
Member

ennru commented Aug 28, 2018

The build failure is fixed in master, please re-base.

Copy link
Member

@ennru ennru left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just another tiny comment. LGTM, otherwise.

case settings: JmsConsumerSettings =>
settings.sessionCount
case settings: JmsConsumerSettings => settings.sessionCount
case settings: JmsProducerSettings => settings.sessionCount
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You may push up sessionCount to JmsSettings to simplify this.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will do :) I try to find some time today to finish things up.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, the JmsBrowseSettings would then need to implement sessionCount as well, which would be hard-coded to 1. Should we go for that?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, makes sense to me.

- created a separate JmsMessageProducer class to keep the stage logic
  more focussed on coordinating elements within the stage.
- grouped the logic for creating the execution context to use in the
  producer stage as well as in the consumer stage.
- JmsProducerStage now supprots multiple sessions by behaving
  similarly to MapAsync
- Producers are pooled and parallelism is controlled by how producers
  are returned to the pool.

Fixes akka#1144
- Introduces traits to separate recoverable and non-recoverable errors
- Introduces exceptions for populate map message and populate message
  properties
- Refactors JmsMessageProducer to produce new exceptions
In order to explain the intent, added some brief comments to the marker
traits.
- Adding tests for new exception types
- Deleting retriable exception marker trait
- renaming NonRetriableException to NonRetriableJmsException
- Adding docs on producer settings
- Adding comments to producer graph stage logic
-
The number of sessions created did not reflect the desired sessionCount.
Now it does.
The contributor advice states that case classes should not be used
as settings objects. Therefore, this commit changes the producer
config docs example from using the case class constructor to using
the withXxx methods.
The previous implementation of the test was flaky, as it did assume that
on failure of one send operation, the producer stage would emit all
in-flight messages before the failure and then fail the stage.

The actual behavior is fail-fast: failure of one send operation causes
immediate failure of the stage. This commit adjusts the test to this
behavior (that corresponds to MapAsync's behavior).
@andreas-schroeder
Copy link
Contributor Author

Test failure is https://travis-ci.org/akka/alpakka/jobs/422226062#L715
I'm not 100% sure, but that test looks flaky to me, as the broker is stopped after the stream is started. If stopping the broker is delayed long enough for the stream to be actually started, a cached connection will be created. I can reproduce the failure with a Thread.sleep(1000) before ctx.broker.stop() here: https://github.com/akka/alpakka/blob/master/jms/src/test/scala/akka/stream/alpakka/jms/scaladsl/JmsConnectorsSpec.scala#L580

The tests "fail fast on the first failing send" and
"sink disconnect exceptional completion" were racy. This commit fixes
these tests by introducing explicit count down latches to wait
for execution progress.
@andreas-schroeder
Copy link
Contributor Author

I've changed the failing test and my test (that was actually racy too) in the hope of improving things.

@andreas-schroeder
Copy link
Contributor Author

well, now I am at a loss at how to stabilize the failing test - any ideas welcome :)

@ennru
Copy link
Member

ennru commented Aug 30, 2018

We've seen failure on that from time to time: #1031
If you find a way to make it stable, please do (eg. in another PR).

@andreas-schroeder
Copy link
Contributor Author

Now, it's an ftp test that fails: https://travis-ci.org/akka/alpakka/jobs/422635814#L644

@ennru ennru merged commit dccffeb into akka:master Aug 31, 2018
@ennru ennru added this to the 0.21 milestone Aug 31, 2018
@ennru
Copy link
Member

ennru commented Aug 31, 2018

Thank you for this great addition to the JMS connector!

sebastianharko pushed a commit to sebastianharko/alpakka that referenced this pull request Sep 5, 2018
- JmsProducerStage now supports multiple sessions by behaving
  similarly to MapAsync
- Producers are pooled and parallelism is controlled by how producers
  are returned to the pool
- Created a separate JmsMessageProducer class to keep the stage logic
  more focussed on coordinating elements within the stage.
- Grouped the logic for creating the execution context to use in the
  producer stage as well as in the consumer stage.

Fixes akka#1144
dannylesnik pushed a commit to dannylesnik/alpakka that referenced this pull request Sep 8, 2018
- JmsProducerStage now supports multiple sessions by behaving
  similarly to MapAsync
- Producers are pooled and parallelism is controlled by how producers
  are returned to the pool
- Created a separate JmsMessageProducer class to keep the stage logic
  more focussed on coordinating elements within the stage.
- Grouped the logic for creating the execution context to use in the
  producer stage as well as in the consumer stage.

Fixes akka#1144
@andreas-schroeder andreas-schroeder deleted the jms-producer-multi-session branch October 17, 2018 08:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants