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

mqtt-streaming: Various fixes #1595

Merged
merged 5 commits into from
Mar 26, 2019
Merged

mqtt-streaming: Various fixes #1595

merged 5 commits into from
Mar 26, 2019

Conversation

longshorej
Copy link
Contributor

@longshorej longshorej commented Mar 19, 2019

A number of commits here that improve the resilience of the mqtt-streaming connector. #1577 remains important as well, but will require an Akka release.

mqtt-streaming: Require a connection id to be specified for client flows

This allows the MQTT session to better track which events are relevant, given that there can be races when old connections are torn down and new ones are established.

mqtt-streaming: Fix a bug where actor state was closed over

This could cause duplicate publications to never be sent to new connections

mqtt-streaming: Republish messages on reconnect only by default

Previously, messages for QoS1/2 were only republished on an interval after not receiving an ack.

It is more conventional to instead republish everything only on connect, and indeed to be compliant for MQTT 5, that is the only time this is allowed.

To accommodate this, the timeouts default to 0, but the previous behavior can still be restored by changing the default producer timeout settings.

@longshorej longshorej changed the title mqtt-streaming: Only publish when reconnecting, not after a timeout mqtt-streaming: Various fixes Mar 20, 2019
@longshorej longshorej marked this pull request as ready for review March 20, 2019 22:58
@longshorej
Copy link
Contributor Author

This is ready for a review @ennru @2m when you have a moment.

/cc @huntc

final case class ConsumerFree(topicName: String) extends Event(ByteString.empty)

final case class PublishReceivedLocally(publish: Publish, publishData: Producer.PublishData)
extends Event(ByteString.empty)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

These and related aren't associated with a connection id, so their connection id is an empty ByteString. This is similar to how the server does things.

context.self ! connect
disconnect(context, data.remote, data)
case (_, event) if event.connectionId.nonEmpty && event.connectionId != data.connectionId =>
Behaviors.same
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This case is responsible for letting empty (thus session bound) or matching connection ids fall through to other cases.

case (_, ReceivedProducerPublishingCommand(command)) =>
command.runWith(Sink.foreach {
case Producer.ForwardPublish(publish, packetId) => data.remote.offer(ForwardPublish(publish, packetId))
case Producer.ForwardPubRel(_, packetId) => data.remote.offer(ForwardPubRel(packetId))
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Closing over old state -- if the client ever reconnected, data.remote would be from an old connection and thus never actually republished.

@@ -226,8 +257,13 @@ import scala.util.{Failure, Success}
)
)
} else {
data.activeProducers.values.foreach { producer =>
producer ! Producer.ReceiveConnect
}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Main change on the client to let the producers know about new ReceiveConnect so that they can republish.

@@ -390,6 +390,9 @@ import scala.util.{Failure, Success}
data.stash.foreach(context.self.tell)
timer.cancel(ReceiveConnAck)

data.activeProducers.values
.foreach(_ ! Producer.ReceiveConnect)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Similarly on the server.

Copy link
Contributor

@huntc huntc left a comment

Choose a reason for hiding this comment

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

Review of the connection id logic: Looking good. One comment, although more of a question than anything that should stop this from being merged.

Copy link
Contributor

@huntc huntc left a comment

Choose a reason for hiding this comment

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

Re. closing over - it took me a few times, but now I get it. Really nice find.

Copy link
Contributor

@huntc huntc left a comment

Choose a reason for hiding this comment

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

Re-republishing on reconnect. Really nicely done.

@huntc
Copy link
Contributor

huntc commented Mar 21, 2019

@ennru @2m All LGTM. Thanks @longshorej !

One thing to note is that the client connection flow's now require a connection id, in a similar way to the server connection flows. This was an oversight in our original APIs.

@ennru
Copy link
Member

ennru commented Mar 21, 2019

Can you add a MiMa filter for everything in the impl package, please?
Than we'll see how much the public API changed.

@huntc
Copy link
Contributor

huntc commented Mar 21, 2019

Can you add a MiMa filter for everything in the impl package, please? Than we'll see how much the public API changed.

I've not used MiMa before, and I've been looking a way for each project to be able to conveniently exclude their impl resident types. Do you know how that can be achieved with ease?

@ennru
Copy link
Member

ennru commented Mar 21, 2019

Fair enough. I'll add it later today.

@huntc
Copy link
Contributor

huntc commented Mar 21, 2019

Thanks @ennru

@ennru
Copy link
Member

ennru commented Mar 21, 2019

Now we're down to the API breaking changes we want to see

  • method clientSessionFlow(akka.stream.alpakka.mqtt.streaming.javadsl.MqttClientSession): akka.stream.javadsl.BidiFlow in object akka.stream.alpakka.mqtt.streaming.javadsl.Mqtt does not have a correspondent in current version
  • method clientSessionFlow(akka.stream.alpakka.mqtt.streaming.scaladsl.MqttClientSession): akka.stream.scaladsl.BidiFlow in object akka.stream.alpakka.mqtt.streaming.scaladsl.Mqtt does not have a correspondent in current version

as those got a new parameter introduced.

@longshorej
Copy link
Contributor Author

Yeah, that's expected as each flow needs a connection id now (similar to the server). This module is considered an "API may change" one still, right? Given that we're still going through hardening exercises that seems reasonable to me.

@huntc
Copy link
Contributor

huntc commented Mar 21, 2019

The lack of a connection id was an oversight on my part initially.

It is all because of this notion of a session with MQTT. A session must distinguish connections. We were doing this on the server side and exposing it in the api, but we weren’t exposing it for the client api. The client session needs to understand which connection is sending a message as there can be a race between an active one shutting down and a new one being established.

Incidentally, your higher level APIs can hide this complexity given that you’re managing the connection entirely. A good candidate for a client connection id is the client port that the TCP socket has (not ther server port!).

HTH.

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.

LGTM.

Technically this module is not marked "API may change" so you should add a clientSessionFlow method to Mqtt without the connectionId and generating one. That method should be deprecated and point to the new version.

We will be very open to do major releases once 1.0 is out when some API needs improvement that can't keep backwards-compatibilty.

This allows the MQTT session to better track which events are relevant,
given that there can be races when old connections are torn down and new
ones are established.
This could cause duplicate publications to never be sent to new
connections
Previously, messages for QoS1/2 were only republished on an interval
after not receiving an ack.

It is more conventional to instead republish everything only on connect,
and indeed to be compliant for MQTT 5, that is the only time this is
allowed.

To accomodate this, the timeouts default to 0, but the previous behavior
can still be restored by changing the default producer timeout settings.
@longshorej
Copy link
Contributor Author

I added the requested deprecated methods, but MiMa is still complaining about something. Any advice @ennru?

@ennru
Copy link
Member

ennru commented Mar 25, 2019

Looks like you removed the MiMa exclusions file, the version I provided is required for it to accept the other changes.

@longshorej
Copy link
Contributor Author

Ah thanks, that must have happened in the rebase. I've readded it.

@ennru ennru merged commit 0ee0ae5 into akka:master Mar 26, 2019
@ennru ennru added this to the 1.0-RC2 milestone Mar 26, 2019
@ennru
Copy link
Member

ennru commented Mar 26, 2019

Thank you @longshorej and @huntc for your continued work in this! Great to see other contributors helping out, as well.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants