fluent-logger-golang
fluent-logger-golang copied to clipboard
Disordered Ack messages will be missed
Currently, the write()
function will wait for the ack message of the message sent. And reading ack messages from a connection will be processed in the order of written messages.
If the series of ack messages are disordered (it can happen), arrived messages will be missed and the original messages will be re-sent to the server. It is not a critical problem (because sending messages are retried), but it may cause a problem about highly heavier traffic with at-least-once configuration.
A possible solution could be (solution is not only this way, of course):
-
write()
will set themsg.ack
value to a Set (with locking) -
write()
lets a goroutine read response messages from the connection - the goroutine will read a message from any of connections passed, and remove an
AckResp.Ack
from the Set (with locking) -
write()
will wait until the ack value will be removed from the Set (or timeout) - if it timed out,
write()
will retry to send the message
This problem will last even after merging #82
@tagomoris Are you already working on this? :slightly_smiling_face:
About the implementation details, I have the following solution in mind that would be lock-free:
- Start an
ackRecv()
goroutine innewWithDialer
whenAck == true
- Use a dedicated channel (eg. named
acksCh
) to communicate betweenwrite()
andackRecv()
- Create an ack channel for each message in
write()
to communicate back fromackRecv()
towrite()
- Send a struct containing the ack ID, the ack channel and bool (with true value) from
write()
toackRecv()
(before actually writing to the connection, to avoid race conditions) - Block the
write()
until either the ack channel or a timeout channel got triggered i. If the the timeout channel got triggered, resend the same struct as 4. but with the bool turned false. - Concurrently read from the
acksCh
and the socket i. When an "ack request" is received fromacksCh
put it in a local map[string]struct{} if the bool is true or remove it if the bool is false. ii. When an ack response is received from the socket, check if there's a matching ID in the local map and send astruct{}
to the appropriate channel and remove it from the local map. - When
Close()
is called,acksCh
got closed i. Set a localshouldClose
bool to true inacksRecv()
ii. When the last ackMessage is received or removed from the local map (cf. 6.) andshouldClose
is true, exit fromacksRecv()
My only concern with this solution is regarding #104.
I've not worked on this yet - no plans for now. @akerouanton Your idea was an alternative. I don't have enough knowledge about the creation cost of dedicated channels, so I can't evaluate which is better - but the idea of using channels seems a golang-ish solution.