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
     }