Skip to content

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

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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)) {
Copy link
Contributor Author

@ningyougang ningyougang Jan 21, 2021

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.

// 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)) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The 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")
Copy link
Contributor Author

@ningyougang ningyougang Jan 29, 2021

Choose a reason for hiding this comment

The 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)) =>
Expand Down Expand Up @@ -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. */
Expand Down Expand Up @@ -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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BTW, when the prewarm pool are backfilled again?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
*/
Expand All @@ -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))
}

Expand Down Expand Up @@ -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)
Copy link
Contributor Author

@ningyougang ningyougang Jan 21, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Due to add logic of whether exist enough memory, so need to adjust the user memory here.

val minCount = 0
val initialCount = 2
val maxCount = 4
Expand Down