-
Notifications
You must be signed in to change notification settings - Fork 4.2k
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
Bulk Load CDK: Unit tests for memory manager #45091
Bulk Load CDK: Unit tests for memory manager #45091
Conversation
The latest updates on your projects. Learn more about Vercel for Git ↗︎ 1 Skipped Deployment
|
} | ||
usedMemoryBytes.addAndGet(memoryBytes) | ||
while (usedMemoryBytes.get() + memoryBytes > availableMemoryBytes) { | ||
releaseUpdates.receive() |
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.
if memoryBytes > availableMemoryBytes
, we just wait forever, no?
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 isn't memory safe. 2 threads could allocate more memory than is available.
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.
I added a throw and a test for it
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.
also, as soon as I typed if
, copilot knew what I was doing :)
class MemoryManager { | ||
private val availableMemoryBytes: Long = Runtime.getRuntime().maxMemory() | ||
class MemoryManager(availableMemoryProvider: AvailableMemoryProvider) { | ||
private val availableMemoryBytes: Long = availableMemoryProvider.availableMemoryBytes |
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.
really should be totalMemoryBytes. Once they're reserved, they're not really available anymore
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.
agreed done
} | ||
|
||
@Test | ||
fun testReserveBlocking() = runTest { |
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.
we need tests imvolving several threads
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.
Yep, it took a lot but I did finally flush out the bug you were afraid of. See testReserveBlockingMultithreaded()
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.
took a lot of efforts, or a lot of jobs?
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.
A lot of jobs to get one error.
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.
yeah, I usually test those by having some king of test-triggered breakpoint in the production code. As much as I dislike having test-only code in production codepath, anything else is always going to be very flaky or involve a lot of jobs/threads/workers...
73f4ed4
to
a7de516
Compare
a7de516
to
b382d98
Compare
@@ -17,31 +19,49 @@ import kotlin.concurrent.withLock | |||
* TODO: Some degree of logging/monitoring around how accurate we're actually being? |
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.
nit: add a TODO: Be more fair. The current implementation will guarantee a first-come first-served order, which means if a function requests a large amount of memory that's not available yet, any subsequent call will block until the large memory request is satisfied, even though there may be enough available memory to serve the smaller requests
val reserved = AtomicBoolean(false) | ||
|
||
try { | ||
withTimeout(5000) { memoryManager.reserveBlocking(900) } |
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.
nit: can we use a lock (or a channel) instead of a timeout here?
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.
I'm testing that it doesn't block, so I'm not sure what else I can do.
} | ||
|
||
@Test | ||
fun testReserveBlocking() = runTest { |
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.
took a lot of efforts, or a lot of jobs?
withContext(Dispatchers.IO) { | ||
memoryManager.reserveBlocking(1000) | ||
Assertions.assertEquals(0, memoryManager.remainingMemoryBytes) | ||
val nIterations = 100000 |
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.
I guess a lot of jobs...
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.
Yeah, 10,000 wasn't enough to fail consistently. 100,000 and I'd always hit it at least once.
} | ||
|
||
mutex.withLock { | ||
while (usedMemoryBytes.get() + memoryBytes > totalMemoryBytes) { |
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.
yeah, I'm pretty sure this is correct.
Just as an interesting case, we could also do without the AtomicLong and a volatile would suffice :)
Thank you for fixing it.
What
AvailableMemoryProvider
for testing