Skip to content
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
8 changes: 7 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,10 @@ obj/
bin/
.svn/

**/.vscode/
**/.vscode/

.project
.DS_Store
.settings

build-artifacts/
74 changes: 31 additions & 43 deletions runtime/src/main/java/com/tns/AppConfig.java
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
package com.tns;

import java.io.File;

import org.json.JSONObject;

import android.os.Build;
import android.util.Log;

class AppConfig {
private enum KnownKeys {
protected enum KnownKeys {
V8FlagsKey("v8Flags", "--expose_gc"),
CodeCacheKey("codeCache", false),
HeapSnapshotScriptKey("heapSnapshotScript", ""),
Expand All @@ -17,19 +15,8 @@ private enum KnownKeys {
GcThrottleTime("gcThrottleTime", 0),
MemoryCheckInterval("memoryCheckInterval", 0),
FreeMemoryRatio("freeMemoryRatio", 0.0),
Profiling("profiling", "");

public static final KnownKeys[] asArray = {
V8FlagsKey,
CodeCacheKey,
HeapSnapshotScriptKey,
SnapshotFile,
ProfilerOutputDirKey,
GcThrottleTime,
MemoryCheckInterval,
FreeMemoryRatio,
Profiling
};
Profiling("profiling", ""),
MarkingMode("markingMode", com.tns.MarkingMode.full);

private final String name;
private final Object defaultValue;
Expand All @@ -42,19 +29,9 @@ private enum KnownKeys {
public String getName() {
return name;
}

public Object getDefaultValue() {
return defaultValue;
}

int getIndex() {
for (int i=0; i<asArray.length; i++) {
if (asArray[i] == this) {
return i;
}
}
return -1;
}
}

private final static String AndroidKey = "android";
Expand All @@ -75,19 +52,19 @@ public AppConfig(File appDir) {
if (rootObject != null) {
if (rootObject.has(KnownKeys.Profiling.getName())) {
String profiling = rootObject.getString(KnownKeys.Profiling.getName());
values[KnownKeys.Profiling.getIndex()] = profiling;
values[KnownKeys.Profiling.ordinal()] = profiling;
}
if (rootObject.has(AndroidKey)) {
JSONObject androidObject = rootObject.getJSONObject(AndroidKey);
if (androidObject.has(KnownKeys.V8FlagsKey.getName())) {
values[KnownKeys.V8FlagsKey.getIndex()] = androidObject.getString(KnownKeys.V8FlagsKey.getName());
values[KnownKeys.V8FlagsKey.ordinal()] = androidObject.getString(KnownKeys.V8FlagsKey.getName());
}
if (androidObject.has(KnownKeys.CodeCacheKey.getName())) {
values[KnownKeys.CodeCacheKey.getIndex()] = androidObject.getBoolean(KnownKeys.CodeCacheKey.getName());
values[KnownKeys.CodeCacheKey.ordinal()] = androidObject.getBoolean(KnownKeys.CodeCacheKey.getName());
}
if (androidObject.has(KnownKeys.HeapSnapshotScriptKey.getName())) {
String value = androidObject.getString(KnownKeys.HeapSnapshotScriptKey.getName());
values[KnownKeys.HeapSnapshotScriptKey.getIndex()] = FileSystem.resolveRelativePath(appDir.getPath(), value, appDir + "/app/");
values[KnownKeys.HeapSnapshotScriptKey.ordinal()] = FileSystem.resolveRelativePath(appDir.getPath(), value, appDir + "/app/");
}
if (androidObject.has(HeapSnapshotBlobKey)) {
String value = androidObject.getString(HeapSnapshotBlobKey);
Expand All @@ -96,20 +73,30 @@ public AppConfig(File appDir) {
if (dir.exists() && dir.isDirectory()) {
// this path is expected to be a directory, containing three sub-directories: armeabi-v7a, x86 and arm64-v8a
path = path + "/" + Build.CPU_ABI + "/" + KnownKeys.SnapshotFile.getName();
values[KnownKeys.SnapshotFile.getIndex()] = path;
values[KnownKeys.SnapshotFile.ordinal()] = path;
}
}
if (androidObject.has(KnownKeys.ProfilerOutputDirKey.getName())) {
values[KnownKeys.ProfilerOutputDirKey.getIndex()] = androidObject.getString(KnownKeys.ProfilerOutputDirKey.getName());
values[KnownKeys.ProfilerOutputDirKey.ordinal()] = androidObject.getString(KnownKeys.ProfilerOutputDirKey.getName());
}
if (androidObject.has(KnownKeys.GcThrottleTime.getName())) {
values[KnownKeys.GcThrottleTime.getIndex()] = androidObject.getInt(KnownKeys.GcThrottleTime.getName());
values[KnownKeys.GcThrottleTime.ordinal()] = androidObject.getInt(KnownKeys.GcThrottleTime.getName());
}
if (androidObject.has(KnownKeys.MemoryCheckInterval.getName())) {
values[KnownKeys.MemoryCheckInterval.getIndex()] = androidObject.getInt(KnownKeys.MemoryCheckInterval.getName());
values[KnownKeys.MemoryCheckInterval.ordinal()] = androidObject.getInt(KnownKeys.MemoryCheckInterval.getName());
}
if (androidObject.has(KnownKeys.FreeMemoryRatio.getName())) {
values[KnownKeys.FreeMemoryRatio.getIndex()] = androidObject.getDouble(KnownKeys.FreeMemoryRatio.getName());
values[KnownKeys.FreeMemoryRatio.ordinal()] = androidObject.getDouble(KnownKeys.FreeMemoryRatio.getName());
}
if (androidObject.has(KnownKeys.MarkingMode.getName())) {
try {
String markingModeString = androidObject.getString(KnownKeys.MarkingMode.getName());
MarkingMode markingMode = MarkingMode.valueOf(markingModeString);
values[KnownKeys.MarkingMode.ordinal()] = markingMode;
} catch(Exception e) {
e.printStackTrace();
Log.v("JS", "Failed to parse marking mode. The default " + ((MarkingMode)KnownKeys.MarkingMode.getDefaultValue()).name() + " will be used.");
}
}
}
}
Expand All @@ -123,27 +110,28 @@ public Object[] getAsArray() {
}

private static Object[] makeDefaultOptions() {
Object[] result = new Object[KnownKeys.asArray.length];
int index = 0;
for (KnownKeys key: KnownKeys.asArray) {
result[index++] = key.getDefaultValue();
Object[] result = new Object[KnownKeys.values().length];
for (KnownKeys key: KnownKeys.values()) {
result[key.ordinal()] = key.getDefaultValue();
}
return result;
}

public int getGcThrottleTime() {
return (int)values[KnownKeys.GcThrottleTime.getIndex()];
return (int)values[KnownKeys.GcThrottleTime.ordinal()];
}

public int getMemoryCheckInterval() {
return (int)values[KnownKeys.MemoryCheckInterval.getIndex()];
return (int)values[KnownKeys.MemoryCheckInterval.ordinal()];
}

public double getFreeMemoryRatio() {
return (double)values[KnownKeys.FreeMemoryRatio.getIndex()];
return (double)values[KnownKeys.FreeMemoryRatio.ordinal()];
}

public String getProfilingMode() {
return (String)values[KnownKeys.Profiling.getIndex()];
return (String)values[KnownKeys.Profiling.ordinal()];
}

public MarkingMode getMarkingMode() { return (MarkingMode)values[KnownKeys.MarkingMode.ordinal()]; }
}
9 changes: 9 additions & 0 deletions runtime/src/main/java/com/tns/MarkingMode.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.tns;

/**
* Created by cankov on 24/08/2017.
*/
enum MarkingMode {
full,
none
}
42 changes: 41 additions & 1 deletion runtime/src/main/java/com/tns/Runtime.java
Original file line number Diff line number Diff line change
Expand Up @@ -202,9 +202,17 @@ public DynamicConfiguration getDynamicConfig() {
return dynamicConfig;
}

@RuntimeCallable
public int getMarkingModeOrdinal() {
if (staticConfiguration != null && staticConfiguration.appConfig != null) {
return staticConfiguration.appConfig.getMarkingMode().ordinal();
} else {
return ((MarkingMode)AppConfig.KnownKeys.MarkingMode.getDefaultValue()).ordinal();
}
}

public static boolean isInitialized() {
Runtime runtime = Runtime.getCurrentRuntime();

return (runtime != null) ? runtime.isInitializedImpl() : false;
}

Expand Down Expand Up @@ -806,6 +814,38 @@ private void makeInstanceWeak(ByteBuffer buff, int length, boolean keepAsWeak) {
}
}

@RuntimeCallable
private boolean makeInstanceWeakAndCheckIfAlive(int javaObjectID) {
if (logger.isEnabled()) {
logger.write("makeInstanceWeakAndCheckIfAlive instance " + javaObjectID);
}
Object instance = strongInstances.get(javaObjectID);
if (instance == null) {
WeakReference<Object> ref = weakInstances.get(javaObjectID);
if (ref == null) {
return false;
} else {
instance = ref.get();
if (instance == null) {
// The Java was moved from strong to weak, and then the Java instance was collected.
weakInstances.remove(javaObjectID);
weakJavaObjectToID.remove(Integer.valueOf(javaObjectID));
return false;
} else {
return true;
}
}
} else {
strongInstances.delete(javaObjectID);
strongJavaObjectToID.remove(instance);

weakJavaObjectToID.put(instance, Integer.valueOf(javaObjectID));
weakInstances.put(javaObjectID, new WeakReference<Object>(instance));

return true;
}
}

@RuntimeCallable
private void checkWeakObjectAreAlive(ByteBuffer input, ByteBuffer output, int length) {
input.position(0);
Expand Down
103 changes: 79 additions & 24 deletions runtime/src/main/jni/ObjectManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,13 @@ using namespace v8;
using namespace std;
using namespace tns;

ObjectManager::ObjectManager(jobject javaRuntimeObject)
: m_javaRuntimeObject(javaRuntimeObject), m_env(JEnv()), m_numberOfGC(0), m_currentObjectId(0), m_cache(NewWeakGlobalRefCallback, DeleteWeakGlobalRefCallback, 1000, this) {
ObjectManager::ObjectManager(jobject javaRuntimeObject) :
m_javaRuntimeObject(javaRuntimeObject),
m_env(JEnv()),
m_numberOfGC(0),
m_currentObjectId(0),
m_cache(NewWeakGlobalRefCallback, DeleteWeakGlobalRefCallback, 1000, this) {

auto runtimeClass = m_env.FindClass("com/tns/Runtime");
assert(runtimeClass != nullptr);

Expand All @@ -31,6 +36,9 @@ ObjectManager::ObjectManager(jobject javaRuntimeObject)
MAKE_INSTANCE_WEAK_BATCH_METHOD_ID = m_env.GetMethodID(runtimeClass, "makeInstanceWeak", "(Ljava/nio/ByteBuffer;IZ)V");
assert(MAKE_INSTANCE_WEAK_BATCH_METHOD_ID != nullptr);

MAKE_INSTANCE_WEAK_AND_CHECK_IF_ALIVE_METHOD_ID = m_env.GetMethodID(runtimeClass, "makeInstanceWeakAndCheckIfAlive", "(I)Z");
assert(MAKE_INSTANCE_WEAK_AND_CHECK_IF_ALIVE_METHOD_ID != nullptr);

CHECK_WEAK_OBJECTS_ARE_ALIVE_METHOD_ID = m_env.GetMethodID(runtimeClass, "checkWeakObjectAreAlive", "(Ljava/nio/ByteBuffer;Ljava/nio/ByteBuffer;I)V");
assert(CHECK_WEAK_OBJECTS_ARE_ALIVE_METHOD_ID != nullptr);

Expand All @@ -45,6 +53,10 @@ ObjectManager::ObjectManager(jobject javaRuntimeObject)

auto useGlobalRefs = m_env.CallStaticBooleanMethod(runtimeClass, useGlobalRefsMethodID);
m_useGlobalRefs = useGlobalRefs == JNI_TRUE;

auto getMarkingModeOrdinalMethodID = m_env.GetMethodID(runtimeClass, "getMarkingModeOrdinal", "()I");
jint markingMode = m_env.CallIntMethod(m_javaRuntimeObject, getMarkingModeOrdinalMethodID);
m_markingMode = static_cast<JavaScriptMarkingMode>(markingMode);
}

void ObjectManager::SetInstanceIsolate(Isolate* isolate) {
Expand All @@ -57,8 +69,10 @@ void ObjectManager::Init(Isolate* isolate) {
auto jsWrapperFunc = jsWrapperFuncTemplate->GetFunction();
m_poJsWrapperFunc = new Persistent<Function>(isolate, jsWrapperFunc);

isolate->AddGCPrologueCallback(ObjectManager::OnGcStartedStatic, kGCTypeAll);
isolate->AddGCEpilogueCallback(ObjectManager::OnGcFinishedStatic, kGCTypeAll);
if (m_markingMode != JavaScriptMarkingMode::None) {
isolate->AddGCPrologueCallback(ObjectManager::OnGcStartedStatic, kGCTypeAll);
isolate->AddGCEpilogueCallback(ObjectManager::OnGcFinishedStatic, kGCTypeAll);
}
}


Expand All @@ -80,32 +94,35 @@ JniLocalRef ObjectManager::GetJavaObjectByJsObject(const Local<Object>& object)

ObjectManager::JSInstanceInfo* ObjectManager::GetJSInstanceInfo(const Local<Object>& object) {
JSInstanceInfo* jsInstanceInfo = nullptr;
if (IsJsRuntimeObject(object)) {
return GetJSInstanceInfoFromRuntimeObject(object);
}
return nullptr;
}

auto isolate = m_isolate;
HandleScope handleScope(isolate);
ObjectManager::JSInstanceInfo* ObjectManager::GetJSInstanceInfoFromRuntimeObject(const Local<Object>& object) {
HandleScope handleScope(m_isolate);

if (IsJsRuntimeObject(object)) {
const int jsInfoIdx = static_cast<int>(MetadataNodeKeys::JsInfo);
auto jsInfo = object->GetInternalField(jsInfoIdx);
if (jsInfo->IsUndefined()) {
//Typescript object layout has an object instance as child of the actual registered instance. checking for that
auto prototypeObject = object->GetPrototype().As<Object>();

if (!prototypeObject.IsEmpty() && prototypeObject->IsObject()) {
DEBUG_WRITE("GetJSInstanceInfo: need to check prototype :%d", prototypeObject->GetIdentityHash());
if (IsJsRuntimeObject(prototypeObject)) {
jsInfo = prototypeObject->GetInternalField(jsInfoIdx);
}
const int jsInfoIdx = static_cast<int>(MetadataNodeKeys::JsInfo);
auto jsInfo = object->GetInternalField(jsInfoIdx);
if (jsInfo->IsUndefined()) {
//Typescript object layout has an object instance as child of the actual registered instance. checking for that
auto prototypeObject = object->GetPrototype().As<Object>();

if (!prototypeObject.IsEmpty() && prototypeObject->IsObject()) {
DEBUG_WRITE("GetJSInstanceInfo: need to check prototype :%d", prototypeObject->GetIdentityHash());
if (IsJsRuntimeObject(prototypeObject)) {
jsInfo = prototypeObject->GetInternalField(jsInfoIdx);
}
}
}

if (!jsInfo.IsEmpty() && jsInfo->IsExternal()) {
auto external = jsInfo.As<External>();
jsInstanceInfo = static_cast<JSInstanceInfo*>(external->Value());
}
if (!jsInfo.IsEmpty() && jsInfo->IsExternal()) {
auto external = jsInfo.As<External>();
return static_cast<JSInstanceInfo*>(external->Value());
}

return jsInstanceInfo;
return nullptr;
}

bool ObjectManager::IsJsRuntimeObject(const v8::Local<v8::Object>& object) {
Expand Down Expand Up @@ -211,7 +228,11 @@ void ObjectManager::Link(const Local<Object>& object, uint32_t javaObjectID, jcl
auto state = new ObjectWeakCallbackState(this, jsInstanceInfo, objectHandle);

// subscribe for JS GC event
objectHandle->SetWeak(state, JSObjectWeakCallbackStatic, WeakCallbackType::kFinalizer);
if (m_markingMode == JavaScriptMarkingMode::None) {
objectHandle->SetWeak(state, JSObjectFinalizerStatic, WeakCallbackType::kFinalizer);
} else {
objectHandle->SetWeak(state, JSObjectWeakCallbackStatic, WeakCallbackType::kFinalizer);
}

auto jsInfoIdx = static_cast<int>(MetadataNodeKeys::JsInfo);

Expand Down Expand Up @@ -263,6 +284,40 @@ void ObjectManager::JSObjectWeakCallbackStatic(const WeakCallbackInfo<ObjectWeak
thisPtr->JSObjectWeakCallback(isolate, callbackState);
}

void ObjectManager::JSObjectFinalizerStatic(const WeakCallbackInfo<ObjectWeakCallbackState>& data) {
ObjectWeakCallbackState* callbackState = data.GetParameter();

ObjectManager* thisPtr = callbackState->thisPtr;

auto isolate = data.GetIsolate();

thisPtr->JSObjectFinalizer(isolate, callbackState);
}

void ObjectManager::JSObjectFinalizer(Isolate* isolate, ObjectWeakCallbackState* callbackState) {
Copy link
Contributor

@Plamen5kov Plamen5kov Aug 24, 2017

Choose a reason for hiding this comment

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

add Handle scope in the beginning of the method to take care of the local handles:

auto isolate = m_isolate;
HandleScope handleScope(isolate);

HandleScope handleScope(m_isolate);
Persistent<Object>* po = callbackState->target;
auto jsInstanceInfo = GetJSInstanceInfoFromRuntimeObject(po->Get(m_isolate));

if (jsInstanceInfo == nullptr) {
po->Reset();
return;
}

auto javaObjectID = jsInstanceInfo->JavaObjectID;
jboolean isJavaInstanceAlive = m_env.CallBooleanMethod(m_javaRuntimeObject, MAKE_INSTANCE_WEAK_AND_CHECK_IF_ALIVE_METHOD_ID, javaObjectID);
if (isJavaInstanceAlive) {
// If the Java instance is alive, keep the JavaScript instance alive.
po->SetWeak(callbackState, JSObjectFinalizerStatic, WeakCallbackType::kFinalizer);
} else {
// If the Java instance is dead, this JavaScript instance can be let die.
delete jsInstanceInfo;
auto jsInfoIdx = static_cast<int>(MetadataNodeKeys::JsInfo);
po->Get(m_isolate)->SetInternalField(jsInfoIdx, Undefined(m_isolate));
po->Reset();
}
}

/*
* When JS GC happens change state of the java counterpart to mirror state of JS object and REVIVE the JS object unconditionally
* "Regular" js objects are pushed into the "regular objects" array
Expand Down
Loading