You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@openwhisk.apache.org by sl...@apache.org on 2018/12/06 10:28:27 UTC
[incubator-openwhisk] branch master updated: Make ActionLimits test
more reliable (#4155)
This is an automated email from the ASF dual-hosted git repository.
slange pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-openwhisk.git
The following commit(s) were added to refs/heads/master by this push:
new 8cb994e Make ActionLimits test more reliable (#4155)
8cb994e is described below
commit 8cb994ea7fe3a3fddebe6bec44b4d762bd3ed259
Author: Chetan Mehrotra <ch...@apache.org>
AuthorDate: Thu Dec 6 15:58:22 2018 +0530
Make ActionLimits test more reliable (#4155)
* In the past, this test occasionally failed when the system was low on memory because the test allocated one large buffer.
* Now, the test gradually allocates smaller buffers and writes random values to them such that physical pages are kept resident for the allocated memory.
---
tests/dat/actions/memoryWithGC.js | 89 ++++++++++++++++++++--
.../openwhisk/core/limits/ActionLimitsTests.scala | 5 +-
2 files changed, 85 insertions(+), 9 deletions(-)
diff --git a/tests/dat/actions/memoryWithGC.js b/tests/dat/actions/memoryWithGC.js
index 35c0a0c..98db2f0 100644
--- a/tests/dat/actions/memoryWithGC.js
+++ b/tests/dat/actions/memoryWithGC.js
@@ -1,16 +1,91 @@
// Licensed to the Apache Software Foundation (ASF) under one or more contributor
// license agreements; and to You under the Apache License, Version 2.0.
-function eat(memoryMB) {
- var bytes = 1*1024*1024*memoryMB;
- var buffer = new Buffer.alloc(bytes, 'a');
- buffer = null;
- console.log('done.');
+'use strict';
+
+// Usually, Linux systems have a page size of 4096 byte
+// The actual value can be obtained with `getconf PAGESIZE`
+const pageSizeInB = 4096;
+
+// This array will be used to store all allocated blocks
+// such that they won't be garbage collected
+let blocks = [];
+
+// Allocates a byte array that has page size
+function allocateMemoryBlock(sizeInB) {
+ return new Uint8Array(sizeInB);
+}
+
+// Returns a random number between 0 (inclusive) and
+// the specified value maxExclusive (exclusive)
+function randomUnsigned(maxExclusive) {
+ return Math.floor(Math.random() * maxExclusive);
+}
+
+// Fills the first 4 bytes of the passed byte array with random
+// numbers
+function fillMemoryPage(byteArray) {
+ for (let i = 0; (i < 4) && (i < pageSizeInB); i++) {
+ byteArray[i] = randomUnsigned(256);
+ }
+}
+
+// Consumes the specified amount of physical memory
+// * The memory is allocated in smaller blocks instead of
+// allocating one large block of memory to prevent
+// virtual OOM
+// * Size of allocated blocks is a multiple of page size
+// * The number of allocated blocks has an upper bound
+// because a reference to each block is stored in an
+// array. If the number of blocks gets too high, the
+// resulting array grows so large that its contribution
+// to memory consumption causes trouble.
+// For this reason, the block size is adjusted to
+// limit the number of blocks. The resulting allocation
+// granularity can cause a slight over-consumption of
+// memory. That's why the upper limit must be selected
+// carefully.
+// * Fill randomly to prevent memory deduplication
+function eat(memoryInMiB) {
+ const memoryInB = memoryInMiB * 1024 * 1024;
+ const memoryInPages = Math.ceil(memoryInB / pageSizeInB);
+ console.log('helloEatMemory: memoryInB=' + memoryInB + ', memoryInPages=' + memoryInPages);
+
+ let blockSizeInB = pageSizeInB;
+ let memoryInBlocks = memoryInPages;
+ let pagesPerBlock = 1;
+ const maxBlocks = 8192;
+ if (memoryInPages > maxBlocks) {
+ pagesPerBlock = Math.ceil(memoryInB / (maxBlocks * pageSizeInB));
+ blockSizeInB = pagesPerBlock * pageSizeInB;
+ memoryInBlocks = Math.ceil(memoryInB / blockSizeInB);
+ }
+ console.log('helloEatMemory: pagesPerBlock=' + pagesPerBlock + ', blockSizeInB=' + blockSizeInB + ', memoryInBlocks=' + memoryInBlocks);
+
+ for (let b = 0; b < memoryInBlocks; b++) {
+ let byteArray = allocateMemoryBlock(blockSizeInB);
+ fillMemoryPage(byteArray);
+ blocks.push(byteArray);
+ }
+ console.log('helloEatMemory: blocks.length=' + blocks.length);
}
function main(msg) {
- console.log('helloEatMemory', 'memory ' + msg.payload + 'MB');
+ console.log('helloEatMemory: memory ' + msg.payload + 'MB');
global.gc();
eat(msg.payload);
+
+ console.log('helloEatMemory: completed allocating memory');
+ console.log(process.memoryUsage());
+
+ // This Node.js code is invoked as Apache OW action
+ // We need to explicitly clear the array such that all
+ // allocated memory gets garbage collected and
+ // we have a "fresh" instance on next invocation
+ // Clean up after ourselves such that the warm container
+ // does not keep memory
+ blocks = [];
+ global.gc();
+
return {msg: 'OK, buffer of size ' + msg.payload + ' MB has been filled.'};
-}
+}
\ No newline at end of file
diff --git a/tests/src/test/scala/org/apache/openwhisk/core/limits/ActionLimitsTests.scala b/tests/src/test/scala/org/apache/openwhisk/core/limits/ActionLimitsTests.scala
index eb10f6e..23523c0 100644
--- a/tests/src/test/scala/org/apache/openwhisk/core/limits/ActionLimitsTests.scala
+++ b/tests/src/test/scala/org/apache/openwhisk/core/limits/ActionLimitsTests.scala
@@ -451,13 +451,14 @@ class ActionLimitsTests extends TestHelpers with WskTestHelpers with WskActorSys
it should "be aborted when exceeding its memory limits" in withAssetCleaner(wskprops) { (wp, assetHelper) =>
val name = "TestNodeJsMemoryExceeding"
assetHelper.withCleaner(wsk.action, name, confirmDelete = true) {
- val allowedMemory = 128.megabytes
+ val allowedMemory = MemoryLimit.minMemory
val actionName = TestUtils.getTestActionFilename("memoryWithGC.js")
(action, _) =>
action.create(name, Some(actionName), memory = Some(allowedMemory))
}
- val run = wsk.action.invoke(name, Map("payload" -> 256.toJson))
+ val payload = MemoryLimit.minMemory.toMB * 2
+ val run = wsk.action.invoke(name, Map("payload" -> payload.toJson))
withActivation(wsk.activation, run) {
_.response.result.get.fields("error") shouldBe Messages.memoryExhausted.toJson
}