-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Don't create prewarm container when used memory reaches the limit #5048
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -129,44 +129,40 @@ class ContainerPool(childFactory: ActorRefFactory => ActorRef, | |
val memory = r.action.limits.memory.megabytes.MB | ||
|
||
val createdContainer = | ||
// Is there enough space on the invoker for this action to be executed. | ||
if (hasPoolSpaceFor(busyPool ++ prewarmedPool, memory)) { | ||
// Schedule a job to a warm container | ||
ContainerPool | ||
.schedule(r.action, r.msg.user.namespace.name, freePool) | ||
.map(container => (container, container._2.initingState)) //warmed, warming, and warmingCold always know their state | ||
.orElse( | ||
// There was no warm/warming/warmingCold container. Try to take a prewarm container or a cold container. | ||
|
||
// Is there enough space to create a new container or do other containers have to be removed? | ||
if (hasPoolSpaceFor(busyPool ++ freePool ++ prewarmedPool, memory)) { | ||
// Schedule a job to a warm container | ||
ContainerPool | ||
.schedule(r.action, r.msg.user.namespace.name, freePool) | ||
.map(container => (container, container._2.initingState)) //warmed, warming, and warmingCold always know their state | ||
.orElse( | ||
// There was no warm/warming/warmingCold container. Try to take a prewarm container or a cold container. | ||
// When take prewarm container, has no need to judge whether user memory is enough | ||
takePrewarmContainer(r.action) | ||
.map(container => (container, "prewarmed")) | ||
.orElse { | ||
// Is there enough space to create a new container or do other containers have to be removed? | ||
if (hasPoolSpaceFor(busyPool ++ freePool ++ prewarmedPool, prewarmStartingPool, memory)) { | ||
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. Add memory check condition for coldStart |
||
val container = Some(createContainer(memory), "cold") | ||
incrementColdStartCount(kind, memory) | ||
container | ||
} else None | ||
}) | ||
.orElse( | ||
// Remove a container and create a new one for the given job | ||
ContainerPool | ||
// Only free up the amount, that is really needed to free up | ||
.remove(freePool, Math.min(r.action.limits.memory.megabytes, memoryConsumptionOf(freePool)).MB) | ||
.map(removeContainer) | ||
// If the list had at least one entry, enough containers were removed to start the new container. After | ||
// removing the containers, we are not interested anymore in the containers that have been removed. | ||
.headOption | ||
.map(_ => | ||
takePrewarmContainer(r.action) | ||
.map(container => (container, "prewarmed")) | ||
.orElse { | ||
val container = Some(createContainer(memory), "cold") | ||
.map(container => (container, "recreatedPrewarm")) | ||
.getOrElse { | ||
val container = (createContainer(memory), "recreated") | ||
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. For here's coldStart, i think has no need to do memory check. because it will delete a container from freePool in advance. |
||
incrementColdStartCount(kind, memory) | ||
container | ||
} | ||
} else None) | ||
.orElse( | ||
// Remove a container and create a new one for the given job | ||
ContainerPool | ||
// Only free up the amount, that is really needed to free up | ||
.remove(freePool, Math.min(r.action.limits.memory.megabytes, memoryConsumptionOf(freePool)).MB) | ||
.map(removeContainer) | ||
// If the list had at least one entry, enough containers were removed to start the new container. After | ||
// removing the containers, we are not interested anymore in the containers that have been removed. | ||
.headOption | ||
.map(_ => | ||
takePrewarmContainer(r.action) | ||
.map(container => (container, "recreatedPrewarm")) | ||
.getOrElse { | ||
val container = (createContainer(memory), "recreated") | ||
incrementColdStartCount(kind, memory) | ||
container | ||
})) | ||
|
||
} else None | ||
})) | ||
|
||
createdContainer match { | ||
case Some(((actor, data), containerState)) => | ||
|
@@ -371,9 +367,15 @@ class ContainerPool(childFactory: ActorRefFactory => ActorRef, | |
|
||
/** Creates a new prewarmed container */ | ||
def prewarmContainer(exec: CodeExec[_], memoryLimit: ByteSize, ttl: Option[FiniteDuration]): Unit = { | ||
val newContainer = childFactory(context) | ||
prewarmStartingPool = prewarmStartingPool + (newContainer -> (exec.kind, memoryLimit)) | ||
newContainer ! Start(exec, memoryLimit, ttl) | ||
if (hasPoolSpaceFor(busyPool ++ freePool ++ prewarmedPool, prewarmStartingPool, memoryLimit)) { | ||
val newContainer = childFactory(context) | ||
prewarmStartingPool = prewarmStartingPool + (newContainer -> (exec.kind, memoryLimit)) | ||
newContainer ! Start(exec, memoryLimit, ttl) | ||
} else { | ||
logging.warn( | ||
this, | ||
s"Cannot create prewarm container due to reach the invoker memory limit: ${poolConfig.userMemory.toMB}") | ||
} | ||
} | ||
|
||
/** this is only for cold start statistics of prewarm configs, e.g. not blackbox or other configs. */ | ||
|
@@ -439,8 +441,10 @@ class ContainerPool(childFactory: ActorRefFactory => ActorRef, | |
* @param memory The amount of memory to check. | ||
* @return true, if there is enough space for the given amount of memory. | ||
*/ | ||
def hasPoolSpaceFor[A](pool: Map[A, ContainerData], memory: ByteSize): Boolean = { | ||
memoryConsumptionOf(pool) + memory.toMB <= poolConfig.userMemory.toMB | ||
def hasPoolSpaceFor[A](pool: Map[A, ContainerData], | ||
prewarmStartingPool: Map[A, (String, ByteSize)], | ||
memory: ByteSize): Boolean = { | ||
memoryConsumptionOf(pool) + prewarmStartingPool.map(_._2._2.toMB).sum + memory.toMB <= poolConfig.userMemory.toMB | ||
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. BTW, when the prewarm pool are backfilled again? 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. |
||
} | ||
|
||
/** | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -310,6 +310,19 @@ class ContainerPoolTests | |
feed.expectMsg(MessageFeed.Processed) | ||
} | ||
|
||
it should "not create prewarm container when used memory reaches the limit" in within(timeout) { | ||
val (containers, factory) = testContainers(2) | ||
val feed = TestProbe() | ||
|
||
val pool = | ||
system.actorOf(ContainerPool | ||
.props(factory, poolConfig(MemoryLimit.STD_MEMORY * 1), feed.ref, List(PrewarmingConfig(2, exec, memoryLimit)))) | ||
containers(0).expectMsg(Start(exec, memoryLimit)) | ||
containers(0).send(pool, NeedWork(preWarmedData(exec.kind))) | ||
|
||
containers(1).expectNoMessage(100.milliseconds) | ||
} | ||
|
||
/* | ||
* CONTAINER PREWARMING | ||
*/ | ||
|
@@ -320,7 +333,7 @@ class ContainerPoolTests | |
val pool = | ||
system.actorOf( | ||
ContainerPool | ||
.props(factory, poolConfig(0.MB), feed.ref, List(PrewarmingConfig(1, exec, memoryLimit)))) | ||
.props(factory, poolConfig(MemoryLimit.STD_MEMORY), feed.ref, List(PrewarmingConfig(1, exec, memoryLimit)))) | ||
containers(0).expectMsg(Start(exec, memoryLimit)) | ||
} | ||
|
||
|
@@ -829,7 +842,7 @@ class ContainerPoolTests | |
stream.reset() | ||
val prewarmExpirationCheckIntervel = 2.seconds | ||
val poolConfig = | ||
ContainerPoolConfig(MemoryLimit.STD_MEMORY * 8, 0.5, false, prewarmExpirationCheckIntervel, None, 100) | ||
ContainerPoolConfig(MemoryLimit.STD_MEMORY * 12, 0.5, false, prewarmExpirationCheckIntervel, None, 100) | ||
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. Due to |
||
val minCount = 0 | ||
val initialCount = 2 | ||
val maxCount = 4 | ||
|
Uh oh!
There was an error while loading. Please reload this page.
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.
Due to
didn't create any container here
, so it is better to remove below logic here.