Skip to content

Commit c6e32b1

Browse files
authored
Don't create prewarm container when used memory reaches the limit (#5048)
* Don't create prewarm container when used memory reaches the limit * Fix review points
1 parent efdbd60 commit c6e32b1

File tree

2 files changed

+59
-42
lines changed

2 files changed

+59
-42
lines changed

core/invoker/src/main/scala/org/apache/openwhisk/core/containerpool/ContainerPool.scala

Lines changed: 44 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -129,44 +129,40 @@ class ContainerPool(childFactory: ActorRefFactory => ActorRef,
129129
val memory = r.action.limits.memory.megabytes.MB
130130

131131
val createdContainer =
132-
// Is there enough space on the invoker for this action to be executed.
133-
if (hasPoolSpaceFor(busyPool ++ prewarmedPool, memory)) {
134-
// Schedule a job to a warm container
135-
ContainerPool
136-
.schedule(r.action, r.msg.user.namespace.name, freePool)
137-
.map(container => (container, container._2.initingState)) //warmed, warming, and warmingCold always know their state
138-
.orElse(
139-
// There was no warm/warming/warmingCold container. Try to take a prewarm container or a cold container.
140-
141-
// Is there enough space to create a new container or do other containers have to be removed?
142-
if (hasPoolSpaceFor(busyPool ++ freePool ++ prewarmedPool, memory)) {
132+
// Schedule a job to a warm container
133+
ContainerPool
134+
.schedule(r.action, r.msg.user.namespace.name, freePool)
135+
.map(container => (container, container._2.initingState)) //warmed, warming, and warmingCold always know their state
136+
.orElse(
137+
// There was no warm/warming/warmingCold container. Try to take a prewarm container or a cold container.
138+
// When take prewarm container, has no need to judge whether user memory is enough
139+
takePrewarmContainer(r.action)
140+
.map(container => (container, "prewarmed"))
141+
.orElse {
142+
// Is there enough space to create a new container or do other containers have to be removed?
143+
if (hasPoolSpaceFor(busyPool ++ freePool ++ prewarmedPool, prewarmStartingPool, memory)) {
144+
val container = Some(createContainer(memory), "cold")
145+
incrementColdStartCount(kind, memory)
146+
container
147+
} else None
148+
})
149+
.orElse(
150+
// Remove a container and create a new one for the given job
151+
ContainerPool
152+
// Only free up the amount, that is really needed to free up
153+
.remove(freePool, Math.min(r.action.limits.memory.megabytes, memoryConsumptionOf(freePool)).MB)
154+
.map(removeContainer)
155+
// If the list had at least one entry, enough containers were removed to start the new container. After
156+
// removing the containers, we are not interested anymore in the containers that have been removed.
157+
.headOption
158+
.map(_ =>
143159
takePrewarmContainer(r.action)
144-
.map(container => (container, "prewarmed"))
145-
.orElse {
146-
val container = Some(createContainer(memory), "cold")
160+
.map(container => (container, "recreatedPrewarm"))
161+
.getOrElse {
162+
val container = (createContainer(memory), "recreated")
147163
incrementColdStartCount(kind, memory)
148164
container
149-
}
150-
} else None)
151-
.orElse(
152-
// Remove a container and create a new one for the given job
153-
ContainerPool
154-
// Only free up the amount, that is really needed to free up
155-
.remove(freePool, Math.min(r.action.limits.memory.megabytes, memoryConsumptionOf(freePool)).MB)
156-
.map(removeContainer)
157-
// If the list had at least one entry, enough containers were removed to start the new container. After
158-
// removing the containers, we are not interested anymore in the containers that have been removed.
159-
.headOption
160-
.map(_ =>
161-
takePrewarmContainer(r.action)
162-
.map(container => (container, "recreatedPrewarm"))
163-
.getOrElse {
164-
val container = (createContainer(memory), "recreated")
165-
incrementColdStartCount(kind, memory)
166-
container
167-
}))
168-
169-
} else None
165+
}))
170166

171167
createdContainer match {
172168
case Some(((actor, data), containerState)) =>
@@ -371,9 +367,15 @@ class ContainerPool(childFactory: ActorRefFactory => ActorRef,
371367

372368
/** Creates a new prewarmed container */
373369
def prewarmContainer(exec: CodeExec[_], memoryLimit: ByteSize, ttl: Option[FiniteDuration]): Unit = {
374-
val newContainer = childFactory(context)
375-
prewarmStartingPool = prewarmStartingPool + (newContainer -> (exec.kind, memoryLimit))
376-
newContainer ! Start(exec, memoryLimit, ttl)
370+
if (hasPoolSpaceFor(busyPool ++ freePool ++ prewarmedPool, prewarmStartingPool, memoryLimit)) {
371+
val newContainer = childFactory(context)
372+
prewarmStartingPool = prewarmStartingPool + (newContainer -> (exec.kind, memoryLimit))
373+
newContainer ! Start(exec, memoryLimit, ttl)
374+
} else {
375+
logging.warn(
376+
this,
377+
s"Cannot create prewarm container due to reach the invoker memory limit: ${poolConfig.userMemory.toMB}")
378+
}
377379
}
378380

379381
/** 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,
439441
* @param memory The amount of memory to check.
440442
* @return true, if there is enough space for the given amount of memory.
441443
*/
442-
def hasPoolSpaceFor[A](pool: Map[A, ContainerData], memory: ByteSize): Boolean = {
443-
memoryConsumptionOf(pool) + memory.toMB <= poolConfig.userMemory.toMB
444+
def hasPoolSpaceFor[A](pool: Map[A, ContainerData],
445+
prewarmStartingPool: Map[A, (String, ByteSize)],
446+
memory: ByteSize): Boolean = {
447+
memoryConsumptionOf(pool) + prewarmStartingPool.map(_._2._2.toMB).sum + memory.toMB <= poolConfig.userMemory.toMB
444448
}
445449

446450
/**

tests/src/test/scala/org/apache/openwhisk/core/containerpool/test/ContainerPoolTests.scala

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -310,6 +310,19 @@ class ContainerPoolTests
310310
feed.expectMsg(MessageFeed.Processed)
311311
}
312312

313+
it should "not create prewarm container when used memory reaches the limit" in within(timeout) {
314+
val (containers, factory) = testContainers(2)
315+
val feed = TestProbe()
316+
317+
val pool =
318+
system.actorOf(ContainerPool
319+
.props(factory, poolConfig(MemoryLimit.STD_MEMORY * 1), feed.ref, List(PrewarmingConfig(2, exec, memoryLimit))))
320+
containers(0).expectMsg(Start(exec, memoryLimit))
321+
containers(0).send(pool, NeedWork(preWarmedData(exec.kind)))
322+
323+
containers(1).expectNoMessage(100.milliseconds)
324+
}
325+
313326
/*
314327
* CONTAINER PREWARMING
315328
*/
@@ -320,7 +333,7 @@ class ContainerPoolTests
320333
val pool =
321334
system.actorOf(
322335
ContainerPool
323-
.props(factory, poolConfig(0.MB), feed.ref, List(PrewarmingConfig(1, exec, memoryLimit))))
336+
.props(factory, poolConfig(MemoryLimit.STD_MEMORY), feed.ref, List(PrewarmingConfig(1, exec, memoryLimit))))
324337
containers(0).expectMsg(Start(exec, memoryLimit))
325338
}
326339

@@ -829,7 +842,7 @@ class ContainerPoolTests
829842
stream.reset()
830843
val prewarmExpirationCheckIntervel = 2.seconds
831844
val poolConfig =
832-
ContainerPoolConfig(MemoryLimit.STD_MEMORY * 8, 0.5, false, prewarmExpirationCheckIntervel, None, 100)
845+
ContainerPoolConfig(MemoryLimit.STD_MEMORY * 12, 0.5, false, prewarmExpirationCheckIntervel, None, 100)
833846
val minCount = 0
834847
val initialCount = 2
835848
val maxCount = 4

0 commit comments

Comments
 (0)