Details
diff --git a/azkaban-common/src/main/java/azkaban/jobExecutor/ProcessJob.java b/azkaban-common/src/main/java/azkaban/jobExecutor/ProcessJob.java
index eff10e3..3a132fc 100644
--- a/azkaban-common/src/main/java/azkaban/jobExecutor/ProcessJob.java
+++ b/azkaban-common/src/main/java/azkaban/jobExecutor/ProcessJob.java
@@ -34,6 +34,9 @@ import azkaban.utils.Pair;
import azkaban.utils.Props;
import azkaban.utils.SystemMemoryInfo;
+import static azkaban.ServiceProvider.*;
+
+
/**
* A job that runs a simple unix command
*/
@@ -47,9 +50,6 @@ public class ProcessJob extends AbstractProcessJob {
private static final String MEMCHECK_ENABLED = "memCheck.enabled";
- private static final String MEMCHECK_FREEMEMDECRAMT =
- "memCheck.freeMemDecrAmt";
-
public static final String AZKABAN_MEMORY_CHECK = "azkaban.memory.check";
public static final String NATIVE_LIB_FOLDER = "azkaban.native.lib";
@@ -75,17 +75,21 @@ public class ProcessJob extends AbstractProcessJob {
if (sysProps.getBoolean(MEMCHECK_ENABLED, true)
&& jobProps.getBoolean(AZKABAN_MEMORY_CHECK, true)) {
- long freeMemDecrAmt = sysProps.getLong(MEMCHECK_FREEMEMDECRAMT, 0);
Pair<Long, Long> memPair = getProcMemoryRequirement();
+ long xms = memPair.getFirst();
+ long xmx = memPair.getSecond();
// retry backoff in ms
String oomMsg = String.format("Cannot request memory (Xms %d kb, Xmx %d kb) from system for job %s",
- memPair.getFirst(), memPair.getSecond(), getId());
+ xms, xmx, getId());
int attempt;
boolean isMemGranted = true;
+
+ //todo HappyRay: move to proper Guice after this class is refactored.
+ SystemMemoryInfo memInfo = SERVICE_PROVIDER.getInstance(SystemMemoryInfo.class);
for(attempt = 1; attempt <= Constants.MEMORY_CHECK_RETRY_LIMIT; attempt++) {
- isMemGranted = SystemMemoryInfo.canSystemGrantMemory(memPair.getFirst(), memPair.getSecond(), freeMemDecrAmt);
+ isMemGranted = memInfo.canSystemGrantMemory(xmx);
if (isMemGranted) {
- info(String.format("Memory granted (Xms %d kb, Xmx %d kb) from system for job %s", memPair.getFirst(), memPair.getSecond(), getId()));
+ info(String.format("Memory granted for job %s", getId()));
if(attempt > 1) {
CommonMetrics.INSTANCE.decrementOOMJobWaitCount();
}
diff --git a/azkaban-common/src/main/java/azkaban/utils/OsMemoryUtil.java b/azkaban-common/src/main/java/azkaban/utils/OsMemoryUtil.java
index 62743b4..1b52522 100644
--- a/azkaban-common/src/main/java/azkaban/utils/OsMemoryUtil.java
+++ b/azkaban-common/src/main/java/azkaban/utils/OsMemoryUtil.java
@@ -1,11 +1,12 @@
package azkaban.utils;
-import java.io.File;
+import com.google.common.collect.ImmutableSet;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.List;
+import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -15,42 +16,33 @@ import org.slf4j.LoggerFactory;
*
* Note:
* This check is designed for Linux only.
- * Make sure to call {@link #doesMemInfoFileExist()} first before attempting to get memory information.
*/
class OsMemoryUtil {
- private static final Logger logger = LoggerFactory.getLogger(SystemMemoryInfo.class);
+ private static final Logger logger = LoggerFactory.getLogger(OsMemoryUtil.class);
// This file is used by Linux. It doesn't exist on Mac for example.
- static final String MEM_INFO_FILE = "/proc/meminfo";
+ private static final String MEM_INFO_FILE = "/proc/meminfo";
- private final String[] MEM_KEYS;
-
- OsMemoryUtil() {
- MEM_KEYS = new String[]{"MemFree", "Buffers", "Cached", "SwapFree"};
- }
-
- /**
- *
- * @return true if the meminfo file exists.
- */
- boolean doesMemInfoFileExist() {
- File f = new File(MEM_INFO_FILE);
- return f.exists() && !f.isDirectory();
- }
+ private static final Set<String> MEM_KEYS = ImmutableSet.of("MemFree", "Buffers", "Cached", "SwapFree");
/**
* Includes OS cache and free swap.
- * @return the total free memory size of the OS. 0 if there is an error.
+ * @return the total free memory size of the OS. 0 if there is an error or the OS doesn't support this memory check.
*/
long getOsTotalFreeMemorySize() {
+ if (!Files.isRegularFile(Paths.get(MEM_INFO_FILE))) {
+ // Mac doesn't support /proc/meminfo for example.
+ return 0;
+ }
+
List<String> lines;
- // The file /proc/meminfo seems to contain only ASCII characters.
+ // The file /proc/meminfo is assumed to contain only ASCII characters.
// The assumption is that the file is not too big. So it is simpler to read the whole file into memory.
try {
lines = Files.readAllLines(Paths.get(MEM_INFO_FILE), StandardCharsets.UTF_8);
} catch (IOException e) {
String errMsg = "Failed to open mem info file: " + MEM_INFO_FILE;
- logger.warn(errMsg, e);
+ logger.error(errMsg, e);
return 0;
}
return getOsTotalFreeMemorySizeFromStrings(lines);
@@ -78,10 +70,10 @@ class OsMemoryUtil {
}
}
- int length = MEM_KEYS.length;
+ int length = MEM_KEYS.size();
if (count != length) {
String errMsg = String.format("Expect %d keys in the meminfo file. Got %d. content: %s", length, count, lines);
- logger.warn(errMsg);
+ logger.error(errMsg);
totalFree = 0;
}
return totalFree;
@@ -110,7 +102,7 @@ class OsMemoryUtil {
return Long.parseLong(sizeString);
} catch (NumberFormatException e) {
String err = "Failed to parse the meminfo file. Line: " + line;
- logger.warn(err);
+ logger.error(err);
return 0;
}
}
diff --git a/azkaban-common/src/main/java/azkaban/utils/SystemMemoryInfo.java b/azkaban-common/src/main/java/azkaban/utils/SystemMemoryInfo.java
index 39a912f..af2cc4e 100644
--- a/azkaban-common/src/main/java/azkaban/utils/SystemMemoryInfo.java
+++ b/azkaban-common/src/main/java/azkaban/utils/SystemMemoryInfo.java
@@ -1,8 +1,6 @@
package azkaban.utils;
-import java.util.concurrent.Executors;
-import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.TimeUnit;
+import com.google.inject.Inject;
import org.slf4j.LoggerFactory;
@@ -14,117 +12,39 @@ import org.slf4j.LoggerFactory;
* Memory information is obtained from /proc/meminfo, so only Unix/Linux like system
* will support this class.
*
- * All the memory size used in this function is in KB
+ * All the memory size used in this function is in KB.
*/
public class SystemMemoryInfo {
- private static final org.slf4j.Logger logger = LoggerFactory.getLogger(SystemMemoryInfo.class);
+ private final OsMemoryUtil util;
- private static boolean memCheckEnabled;
+ private static final org.slf4j.Logger logger = LoggerFactory.getLogger(SystemMemoryInfo.class);
private static final long LOW_MEM_THRESHOLD = 3L * 1024L * 1024L; //3 GB
- // In case there is a problem reading the meminfo file, we want to "fail open".
- private static long freeMemAmount = LOW_MEM_THRESHOLD * 100;
-
- private static ScheduledExecutorService scheduledExecutorService;
-
- //todo HappyRay: switch to Guice
- private static OsMemoryUtil util = new OsMemoryUtil();
- @SuppressWarnings("FutureReturnValueIgnored")
- // see http://errorprone.info/bugpattern/FutureReturnValueIgnored
- // There is no need to check the returned future from scheduledExecutorService
- // since we don't need to get a return value.
- public static void init(int memCheckInterval) {
- memCheckEnabled = util.doesMemInfoFileExist();
- if (memCheckEnabled) {
- //schedule a thread to read it
- logger.info(String.format("Scheduled thread to read memory info every %d seconds", memCheckInterval));
- scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
- /*
- According to java.util.concurrent.Executors.newSingleThreadScheduledExecutor()
- * (Note however that if this single
- * thread terminates due to a failure during execution prior to
- * shutdown, a new one will take its place if needed to execute
- * subsequent tasks.)
- * We don't have to worry about the update thread dying due to an uncaught exception.
- */
- scheduledExecutorService.scheduleAtFixedRate(SystemMemoryInfo::getFreeMemorySize, 0, memCheckInterval,
- TimeUnit.SECONDS);
- } else {
- logger.info(String.format("Cannot find %s, memory check will be disabled", OsMemoryUtil.MEM_INFO_FILE));
- }
+ @Inject
+ public SystemMemoryInfo(OsMemoryUtil util) {
+ this.util = util;
}
/**
- * @param xms Xms for the process
* @param xmx Xmx for the process
- * @return System can satisfy the memory request or not
+ * @return true if the system can satisfy the memory request
*
- * Given Xms/Xmx values (in kb) used by java process, determine if system can
+ * Given Xmx value (in kb) used by java process, determine if system can
* satisfy the memory request
*/
- public synchronized static boolean canSystemGrantMemory(long xms, long xmx, long freeMemDecrAmt) {
- if (!memCheckEnabled) {
+ public boolean canSystemGrantMemory(long xmx) {
+ long freeMemSize = util.getOsTotalFreeMemorySize();
+ if (freeMemSize == 0) {
+ // Fail open.
+ // On the platforms that don't support the mem info file, the returned size will be 0.
return true;
}
-
- //too small amount of memory left, reject
- if (freeMemAmount < LOW_MEM_THRESHOLD) {
- logger.info(
- String.format("Free memory amount (%d kb) is less than low mem threshold (%d kb), memory request declined.",
- freeMemAmount, LOW_MEM_THRESHOLD));
- return false;
- }
-
- //let's get newest mem info
- if (freeMemAmount >= LOW_MEM_THRESHOLD && freeMemAmount < 2 * LOW_MEM_THRESHOLD) {
- logger.info(String.format(
- "Free memory amount (%d kb) is less than 2x low mem threshold (%d kb). Update the free memory amount",
- freeMemAmount, LOW_MEM_THRESHOLD));
- getFreeMemorySize();
- }
-
- //too small amount of memory left, reject
- if (freeMemAmount < LOW_MEM_THRESHOLD) {
- logger.info(
- String.format("Free memory amount (%d kb) is less than low mem threshold (%d kb), memory request declined.",
- freeMemAmount, LOW_MEM_THRESHOLD));
- return false;
- }
-
- if (freeMemAmount - xmx < LOW_MEM_THRESHOLD) {
+ if (freeMemSize - xmx < LOW_MEM_THRESHOLD) {
logger.info(String.format(
- "Free memory amount minus xmx (%d - %d kb) is less than low mem threshold (%d kb), memory request declined.",
- freeMemAmount, xmx, LOW_MEM_THRESHOLD));
+ "Free memory amount minus Xmx (%d - %d kb) is less than low mem threshold (%d kb), memory request declined.",
+ freeMemSize, xmx, LOW_MEM_THRESHOLD));
return false;
}
-
- if (freeMemDecrAmt > 0) {
- freeMemAmount -= freeMemDecrAmt;
- logger.info(
- String.format("Memory (%d kb) granted. Current free memory amount is %d kb", freeMemDecrAmt, freeMemAmount));
- } else {
- freeMemAmount -= xms;
- logger.info(String.format("Memory (%d kb) granted. Current free memory amount is %d kb", xms, freeMemAmount));
- }
-
return true;
}
-
- private synchronized static void updateFreeMemAmount(long size) {
- freeMemAmount = size;
- }
-
- private static void getFreeMemorySize() {
- long freeMemorySize = util.getOsTotalFreeMemorySize();
- if (freeMemorySize > 0) {
- updateFreeMemAmount(freeMemorySize);
- }
- }
-
- public static void shutdown() {
- logger.warn("Shutting down SystemMemoryInfo...");
- if (scheduledExecutorService != null) {
- scheduledExecutorService.shutdown();
- }
- }
}
diff --git a/azkaban-common/src/test/java/azkaban/jobExecutor/JavaProcessJobTest.java b/azkaban-common/src/test/java/azkaban/jobExecutor/JavaProcessJobTest.java
index cc284af..a6f27f4 100644
--- a/azkaban-common/src/test/java/azkaban/jobExecutor/JavaProcessJobTest.java
+++ b/azkaban-common/src/test/java/azkaban/jobExecutor/JavaProcessJobTest.java
@@ -16,11 +16,11 @@
package azkaban.jobExecutor;
-import java.io.IOException;
+import azkaban.utils.Props;
import java.io.File;
+import java.io.IOException;
import java.util.Date;
import java.util.Properties;
-
import org.apache.log4j.Logger;
import org.junit.After;
import org.junit.AfterClass;
@@ -28,12 +28,10 @@ import org.junit.Assert;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.ClassRule;
-import org.junit.Test;
import org.junit.Rule;
+import org.junit.Test;
import org.junit.rules.TemporaryFolder;
-import azkaban.flow.CommonJobProperties;
-import azkaban.utils.Props;
public class JavaProcessJobTest {
@ClassRule
@@ -73,6 +71,7 @@ public class JavaProcessJobTest {
@BeforeClass
public static void init() throws IOException {
+ azkaban.test.Utils.initServiceProvider();
// Get the classpath
Properties prop = System.getProperties();
classPaths =
diff --git a/azkaban-common/src/test/java/azkaban/jobExecutor/ProcessJobTest.java b/azkaban-common/src/test/java/azkaban/jobExecutor/ProcessJobTest.java
index 44512eb..46ed2fe 100644
--- a/azkaban-common/src/test/java/azkaban/jobExecutor/ProcessJobTest.java
+++ b/azkaban-common/src/test/java/azkaban/jobExecutor/ProcessJobTest.java
@@ -16,19 +16,19 @@
package azkaban.jobExecutor;
+import azkaban.flow.CommonJobProperties;
+import azkaban.utils.Props;
import java.io.File;
import java.io.IOException;
-
import org.apache.log4j.Logger;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
-import org.junit.Test;
+import org.junit.BeforeClass;
import org.junit.Rule;
+import org.junit.Test;
import org.junit.rules.TemporaryFolder;
-import azkaban.flow.CommonJobProperties;
-import azkaban.utils.Props;
public class ProcessJobTest {
@Rule
@@ -38,6 +38,12 @@ public class ProcessJobTest {
private Props props = null;
private Logger log = Logger.getLogger(ProcessJob.class);
+ @BeforeClass
+ public static void classInit() throws Exception {
+ azkaban.test.Utils.initServiceProvider();
+
+ }
+
@Before
public void setUp() throws IOException {
File workingDir = temp.newFolder("TestProcess");
diff --git a/azkaban-common/src/test/java/azkaban/test/Utils.java b/azkaban-common/src/test/java/azkaban/test/Utils.java
new file mode 100644
index 0000000..e7b2c78
--- /dev/null
+++ b/azkaban-common/src/test/java/azkaban/test/Utils.java
@@ -0,0 +1,23 @@
+package azkaban.test;
+
+import com.google.inject.AbstractModule;
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+
+import static azkaban.ServiceProvider.*;
+
+
+public class Utils {
+ public static void initServiceProvider() {
+ Injector injector = Guice.createInjector(new AbstractModule() {
+ @Override
+ protected void configure() {
+ }
+ });
+ // Because SERVICE_PROVIDER is a singleton and it is shared among many tests,
+ // need to reset the state to avoid assertion failures.
+ SERVICE_PROVIDER.unsetInjector();
+
+ SERVICE_PROVIDER.setInjector(injector);
+ }
+}
diff --git a/azkaban-common/src/test/java/azkaban/utils/OsMemoryUtilTest.java b/azkaban-common/src/test/java/azkaban/utils/OsMemoryUtilTest.java
index 39390b0..454f05a 100644
--- a/azkaban-common/src/test/java/azkaban/utils/OsMemoryUtilTest.java
+++ b/azkaban-common/src/test/java/azkaban/utils/OsMemoryUtilTest.java
@@ -1,5 +1,8 @@
package azkaban.utils;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
@@ -13,9 +16,13 @@ public class OsMemoryUtilTest {
@Test
public void canReadMemInfoFileIfExists() {
- if (util.doesMemInfoFileExist()) {
- util.getOsTotalFreeMemorySize();
+ long size = util.getOsTotalFreeMemorySize();
+ Path memFile = Paths.get("/proc/meminfo");
+ if (!(Files.isRegularFile(memFile) && Files.isReadable(memFile))) {
+ assertTrue(size == 0);
}
+ // todo HappyRay: investigate why size returned is 0 on Travis only but works on my Linux machine.
+ // I can't find a way to get to the Gradle test report on Travis which makes debugging difficult.
}
@Test
diff --git a/azkaban-common/src/test/java/azkaban/utils/SystemMemoryInfoTest.java b/azkaban-common/src/test/java/azkaban/utils/SystemMemoryInfoTest.java
new file mode 100644
index 0000000..9412c1d
--- /dev/null
+++ b/azkaban-common/src/test/java/azkaban/utils/SystemMemoryInfoTest.java
@@ -0,0 +1,43 @@
+package azkaban.utils;
+
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+import static org.mockito.Mockito.*;
+
+
+public class SystemMemoryInfoTest {
+ private static final long GB_UNIT = 1024L * 1024L;
+
+ @Test
+ public void grantedIfFreeMemoryAvailable() throws Exception {
+ OsMemoryUtil memUtil = mock(OsMemoryUtil.class);
+ long availableFreeMem = 10L * 1024L * 1024L; //10 GB
+ when(memUtil.getOsTotalFreeMemorySize()).thenReturn(availableFreeMem);
+ SystemMemoryInfo memInfo = new SystemMemoryInfo(memUtil);
+ boolean isGranted = memInfo.canSystemGrantMemory(1);
+ assertTrue(isGranted);
+ }
+
+ @Test
+ public void notGrantedIfFreeMemoryAvailableLessThanMinimal() throws Exception {
+ OsMemoryUtil memUtil = mock(OsMemoryUtil.class);
+ long availableFreeMem = 4L * 1024L * 1024L; //4 GB
+ when(memUtil.getOsTotalFreeMemorySize()).thenReturn(availableFreeMem);
+ SystemMemoryInfo memInfo = new SystemMemoryInfo(memUtil);
+ long xmx = 2 * GB_UNIT; //2 GB
+ boolean isGranted = memInfo.canSystemGrantMemory(xmx);
+ assertFalse(isGranted);
+ }
+
+ @Test
+ public void grantedIfFreeMemoryCheckReturnsZero() throws Exception {
+ OsMemoryUtil memUtil = mock(OsMemoryUtil.class);
+ long availableFreeMem = 0;
+ when(memUtil.getOsTotalFreeMemorySize()).thenReturn(availableFreeMem);
+ SystemMemoryInfo memInfo = new SystemMemoryInfo(memUtil);
+ long xmx = 0;
+ boolean isGranted = memInfo.canSystemGrantMemory(xmx);
+ assertTrue("Memory check failed. Should fail open", isGranted);
+ }
+}
diff --git a/azkaban-exec-server/src/main/java/azkaban/execapp/AzkabanExecutorServer.java b/azkaban-exec-server/src/main/java/azkaban/execapp/AzkabanExecutorServer.java
index 7cd3cb6..a669d25 100644
--- a/azkaban-exec-server/src/main/java/azkaban/execapp/AzkabanExecutorServer.java
+++ b/azkaban-exec-server/src/main/java/azkaban/execapp/AzkabanExecutorServer.java
@@ -17,38 +17,7 @@
package azkaban.execapp;
import azkaban.AzkabanCommonModule;
-import com.google.common.base.Throwables;
-
-import com.google.inject.Guice;
-import com.google.inject.Inject;
-import org.apache.commons.lang.StringUtils;
-import org.apache.log4j.Logger;
-import org.joda.time.DateTimeZone;
-import org.mortbay.jetty.Connector;
-import org.mortbay.jetty.Server;
-import org.mortbay.jetty.servlet.Context;
-import org.mortbay.jetty.servlet.ServletHolder;
-import org.mortbay.thread.QueuedThreadPool;
-
-import java.io.BufferedWriter;
-import java.io.File;
-import java.io.FileWriter;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.lang.management.ManagementFactory;
-import java.lang.reflect.Constructor;
-import java.net.InetAddress;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.TimeZone;
-
-import javax.management.MBeanInfo;
-import javax.management.MBeanServer;
-import javax.management.ObjectName;
-
import azkaban.Constants;
-
import azkaban.execapp.event.JobCallbackManager;
import azkaban.execapp.jmx.JmxFlowRunnerManager;
import azkaban.execapp.jmx.JmxJobMBeanManager;
@@ -60,25 +29,47 @@ import azkaban.execapp.metric.NumRunningJobMetric;
import azkaban.executor.Executor;
import azkaban.executor.ExecutorLoader;
import azkaban.executor.ExecutorManagerException;
-import azkaban.executor.JdbcExecutorLoader;
import azkaban.jmx.JmxJettyServer;
import azkaban.metric.IMetricEmitter;
import azkaban.metric.MetricException;
import azkaban.metric.MetricReportManager;
import azkaban.metric.inmemoryemitter.InMemoryMetricEmitter;
-import azkaban.project.JdbcProjectLoader;
-import azkaban.project.ProjectLoader;
+import azkaban.metrics.MetricsManager;
import azkaban.server.AzkabanServer;
import azkaban.utils.Props;
import azkaban.utils.StdOutErrRedirect;
-import azkaban.utils.SystemMemoryInfo;
import azkaban.utils.Utils;
-import azkaban.metrics.MetricsManager;
+import com.google.common.base.Throwables;
+import com.google.inject.Guice;
+import com.google.inject.Inject;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.lang.management.ManagementFactory;
+import java.lang.reflect.Constructor;
+import java.net.InetAddress;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.TimeZone;
+import javax.management.MBeanInfo;
+import javax.management.MBeanServer;
+import javax.management.ObjectName;
+import org.apache.commons.lang.StringUtils;
+import org.apache.log4j.Logger;
+import org.joda.time.DateTimeZone;
+import org.mortbay.jetty.Connector;
+import org.mortbay.jetty.Server;
+import org.mortbay.jetty.servlet.Context;
+import org.mortbay.jetty.servlet.ServletHolder;
+import org.mortbay.thread.QueuedThreadPool;
-import static azkaban.Constants.AZKABAN_EXECUTOR_PORT_FILENAME;
+import static azkaban.Constants.*;
import static azkaban.ServiceProvider.*;
-import static com.google.common.base.Preconditions.checkState;
-import static java.util.Objects.requireNonNull;
+import static com.google.common.base.Preconditions.*;
+import static java.util.Objects.*;
public class AzkabanExecutorServer {
private static final String CUSTOM_JMX_ATTRIBUTE_PROCESSOR_PROPERTY = "jmx.attribute.processor.class";
@@ -120,8 +111,6 @@ public class AzkabanExecutorServer {
configureMBeanServer();
configureMetricReports();
- SystemMemoryInfo.init(props.getInt("executor.memCheck.interval", 30));
-
loadCustomJMXAttributeProcessor(props);
try {
@@ -573,7 +562,6 @@ public class AzkabanExecutorServer {
public void shutdownNow() throws Exception {
server.stop();
server.destroy();
- SystemMemoryInfo.shutdown();
getFlowRunnerManager().shutdownNow();
close();
}