-
Notifications
You must be signed in to change notification settings - Fork 36
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
fix(r2dbc): Leak of in-flight offsets #1300
Merged
Merged
Changes from 1 commit
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -187,6 +187,20 @@ private[projection] object R2dbcOffsetStore { | |
} | ||
} | ||
|
||
override def toString: String = { | ||
val sb = new StringBuilder | ||
sb.append("State(") | ||
bySliceSorted.toVector.sortBy(_._1).foreach { | ||
case (slice, records) => | ||
sb.append("slice ").append(slice).append(": ") | ||
records.foreach { r => | ||
sb.append("[").append(r.pid).append("->").append(r.seqNr).append(" ").append(r.timestamp).append("] ") | ||
} | ||
} | ||
sb.append(")") | ||
sb.toString | ||
} | ||
|
||
} | ||
|
||
final class RejectedEnvelope(message: String) extends IllegalStateException(message) | ||
|
@@ -333,10 +347,10 @@ private[projection] class R2dbcOffsetStore( | |
timestampQuery.timestampOf(persistenceId, sequenceNr).asScala.map(_.toScala) | ||
case Some(_) => | ||
throw new IllegalArgumentException( | ||
s"Expected BySlicesSourceProvider to implement EventTimestampQuery when TimestampOffset is used.") | ||
s"$logPrefix Expected BySlicesSourceProvider to implement EventTimestampQuery when TimestampOffset is used.") | ||
case None => | ||
throw new IllegalArgumentException( | ||
s"Expected BySlicesSourceProvider to be defined when TimestampOffset is used.") | ||
s"$logPrefix Expected BySlicesSourceProvider to be defined when TimestampOffset is used.") | ||
} | ||
} | ||
|
||
|
@@ -353,6 +367,10 @@ private[projection] class R2dbcOffsetStore( | |
} | ||
} | ||
|
||
private def dumpState(s: State, flight: Map[Pid, SeqNr]): String = { | ||
s"$s inFlight [${flight.map { case (pid, seqNr) => s"$pid->$seqNr" }.mkString(",")}]" | ||
} | ||
|
||
def readOffset[Offset](): Future[Option[Offset]] = { | ||
scheduleTasks() | ||
|
||
|
@@ -441,7 +459,9 @@ private[projection] class R2dbcOffsetStore( | |
startOffset) | ||
|
||
if (!state.compareAndSet(oldState, newState)) | ||
throw new IllegalStateException("Unexpected concurrent modification of state from readOffset.") | ||
throw new IllegalStateException( | ||
s"$logPrefix Unexpected concurrent modification of state from readOffset. " + | ||
s"${dumpState(oldState, getInflight())}") | ||
|
||
startOffset | ||
} | ||
|
@@ -540,7 +560,8 @@ private[projection] class R2dbcOffsetStore( | |
val record = Record(slice, pid, seqNr, t.timestamp) | ||
saveTimestampOffsetInTx(conn, Vector(record), canBeConcurrent = true) | ||
case OffsetPidSeqNr(_: TimestampOffset, None) => | ||
throw new IllegalArgumentException("Required EventEnvelope or DurableStateChange for TimestampOffset.") | ||
throw new IllegalArgumentException( | ||
s"$logPrefix Required EventEnvelope or DurableStateChange for TimestampOffset.") | ||
case _ => | ||
savePrimitiveOffsetInTx(conn, offset.offset) | ||
} | ||
|
@@ -569,10 +590,11 @@ private[projection] class R2dbcOffsetStore( | |
val slice = persistenceExt.sliceForPersistenceId(pid) | ||
Record(slice, pid, seqNr, t.timestamp) | ||
case OffsetPidSeqNr(_: TimestampOffset, None) => | ||
throw new IllegalArgumentException("Required EventEnvelope or DurableStateChange for TimestampOffset.") | ||
throw new IllegalArgumentException( | ||
s"$logPrefix Required EventEnvelope or DurableStateChange for TimestampOffset.") | ||
case _ => | ||
throw new IllegalArgumentException( | ||
"Mix of TimestampOffset and other offset type in same transaction is not supported") | ||
s"$logPrefix Mix of TimestampOffset and other offset type in same transaction is not supported") | ||
} | ||
saveTimestampOffsetInTx(conn, records, canBeConcurrent) | ||
} else { | ||
|
@@ -630,15 +652,19 @@ private[projection] class R2dbcOffsetStore( | |
|
||
val offsetInserts = dao.insertTimestampOffsetInTx(conn, filteredRecords) | ||
|
||
offsetInserts.map { _ => | ||
offsetInserts.flatMap { _ => | ||
if (state.compareAndSet(oldState, evictedNewState)) { | ||
slices.foreach(s => triggerDeletionPerSlice.put(s, TRUE)) | ||
cleanupInflight(evictedNewState) | ||
FutureDone | ||
} else { // concurrent update | ||
if (canBeConcurrent) saveTimestampOffsetInTx(conn, records, canBeConcurrent) // CAS retry | ||
else throw new IllegalStateException("Unexpected concurrent modification of state from saveOffset.") | ||
else | ||
throw new IllegalStateException( | ||
s"$logPrefix Unexpected concurrent modification of state from saveOffset. " + | ||
s"${dumpState(newState, currentInflight)}") | ||
} | ||
Done | ||
|
||
} | ||
} | ||
} | ||
|
@@ -656,8 +682,9 @@ private[projection] class R2dbcOffsetStore( | |
} | ||
if (newInflight.size >= 10000) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I have also tested this in a real service where I logged the inFlight size and could see that it was increasing over time. After the fix it is 0 after each save offsets, as expected. |
||
throw new IllegalStateException( | ||
s"Too many envelopes in-flight [${newInflight.size}]. " + | ||
"Please report this issue at https://github.com/akka/akka-projection") | ||
s"$logPrefix Too many envelopes in-flight [${newInflight.size}]. " + | ||
"Please report this issue at https://github.com/akka/akka-projection " + | ||
s"${dumpState(newState, newInflight)}") | ||
} | ||
if (!inflight.compareAndSet(currentInflight, newInflight)) | ||
cleanupInflight(newState) // CAS retry, concurrent update of inflight | ||
|
@@ -1197,7 +1224,7 @@ private[projection] class R2dbcOffsetStore( | |
case change: DurableStateChange[_] if change.offset.isInstanceOf[TimestampOffset] => | ||
// in case additional types are added | ||
throw new IllegalArgumentException( | ||
s"DurableStateChange [${change.getClass.getName}] not implemented yet. Please report bug at https://github.com/akka/akka-projection/issues") | ||
s"$logPrefix DurableStateChange [${change.getClass.getName}] not implemented yet. Please report bug at https://github.com/akka/akka-projection/issues") | ||
case _ => None | ||
} | ||
} | ||
|
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was the bug. I think it was introduced in a recent version when the lazy loading of offsets was added.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍🏼 Not noticed in #1255