diff --git a/CHANGELOG.md b/CHANGELOG.md
index e707ff2..1422f95 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,8 +3,12 @@
* @Author: ekibun
* @Date: 2020-08-08 08:16:50
* @LastEditors: ekibun
- * @LastEditTime: 2020-10-03 00:28:18
+ * @LastEditTime: 2020-10-03 23:34:30
-->
+## 0.1.2
+
+* fix qjs memory leak.
+
## 0.1.1
* run on isolate.
diff --git a/README.md b/README.md
index cc00f95..afa9418 100644
--- a/README.md
+++ b/README.md
@@ -3,7 +3,7 @@
* @Author: ekibun
* @Date: 2020-08-08 08:16:50
* @LastEditors: ekibun
- * @LastEditTime: 2020-10-03 00:36:36
+ * @LastEditTime: 2020-10-03 00:44:41
-->
# flutter_qjs
@@ -67,7 +67,7 @@ channel("http", ["http://example.com/"]);
~~I cannot find a way to convert the sync ffi callback into an async function. So the assets files received by async function `rootBundle.loadString` cannot be used in this version. I will appreciate it if you can provide me a solution to make `ModuleHandler` async.~~
-To use async function in module handler, try [Run on isolate thread](#isolate)
+To use async function in module handler, try [Run on isolate thread](#Run-on-isolate-thread)
```dart
await engine.setModuleHandler((String module) {
@@ -94,7 +94,7 @@ try {
5. Method `recreate` can destroy quickjs runtime that can be recreated again if you call `evaluate`, `recreat` can be used to reset the module cache. Call `close` to stop `dispatch` when you do not need it.
-### Run on isolate thread
+### Run on isolate thread
1. Create a `IsolateQjs` object, pass a handler to implement js-dart interaction. The handler is used in isolate, so the function must be a top-level function or a static method.
diff --git a/cxx/ffi.cpp b/cxx/ffi.cpp
index b766e1d..af3bf6c 100644
--- a/cxx/ffi.cpp
+++ b/cxx/ffi.cpp
@@ -3,7 +3,7 @@
* @Author: ekibun
* @Date: 2020-09-06 18:32:45
* @LastEditors: ekibun
- * @LastEditTime: 2020-09-24 22:32:15
+ * @LastEditTime: 2020-10-03 23:26:14
*/
#include "ffi.h"
#include
@@ -226,7 +226,7 @@ extern "C"
}
DLLEXPORT int32_t jsDefinePropertyValue(JSContext *ctx, JSValueConst *this_obj,
- JSAtom prop, JSValue *val, int32_t flags)
+ JSAtom prop, JSValue *val, int32_t flags)
{
return JS_DefinePropertyValue(ctx, *this_obj, prop, *val, flags);
}
@@ -247,7 +247,7 @@ extern "C"
}
DLLEXPORT int32_t jsGetOwnPropertyNames(JSContext *ctx, JSPropertyEnum **ptab,
- uint32_t *plen, JSValueConst *obj, int32_t flags)
+ uint32_t *plen, JSValueConst *obj, int32_t flags)
{
return JS_GetOwnPropertyNames(ctx, ptab, plen, *obj, flags);
}
@@ -259,7 +259,7 @@ extern "C"
DLLEXPORT uint32_t sizeOfJSValue()
{
- return sizeof (JSValue);
+ return sizeof(JSValue);
}
DLLEXPORT void setJSValueList(JSValue *list, uint32_t i, JSValue *val)
@@ -293,4 +293,9 @@ extern "C"
{
return new JSValue(JS_NewPromiseCapability(ctx, resolving_funcs));
}
+
+ DLLEXPORT void jsFree(JSContext *ctx, void *ptab)
+ {
+ js_free(ctx, ptab);
+ }
}
\ No newline at end of file
diff --git a/cxx/ffi.h b/cxx/ffi.h
index 4fdeff8..7f2b299 100644
--- a/cxx/ffi.h
+++ b/cxx/ffi.h
@@ -106,4 +106,6 @@ extern "C"
DLLEXPORT int32_t jsExecutePendingJob(JSRuntime *rt);
DLLEXPORT JSValue *jsNewPromiseCapability(JSContext *ctx, JSValue *resolving_funcs);
+
+ DLLEXPORT void jsFree(JSContext *ctx, void *ptab);
}
\ No newline at end of file
diff --git a/example/lib/main.dart b/example/lib/main.dart
index 0228080..7d89ef0 100644
--- a/example/lib/main.dart
+++ b/example/lib/main.dart
@@ -3,7 +3,7 @@
* @Author: ekibun
* @Date: 2020-08-08 08:16:51
* @LastEditors: ekibun
- * @LastEditTime: 2020-10-03 00:38:41
+ * @LastEditTime: 2020-10-03 21:37:22
*/
import 'package:flutter/material.dart';
import 'dart:typed_data';
@@ -103,8 +103,8 @@ class _TestPageState extends State {
onPressed: () async {
_ensureEngine();
try {
- resp = (await engine.evaluate(
- _controller.text ?? '', ""))
+ resp = (await engine.evaluate(_controller.text ?? '',
+ name: ""))
.toString();
} catch (e) {
resp = e.toString();
diff --git a/example/pubspec.lock b/example/pubspec.lock
index b017748..312ef0c 100644
--- a/example/pubspec.lock
+++ b/example/pubspec.lock
@@ -7,42 +7,42 @@ packages:
name: async
url: "https://pub.flutter-io.cn"
source: hosted
- version: "2.5.0-nullsafety"
+ version: "2.5.0-nullsafety.1"
boolean_selector:
dependency: transitive
description:
name: boolean_selector
url: "https://pub.flutter-io.cn"
source: hosted
- version: "2.1.0-nullsafety"
+ version: "2.1.0-nullsafety.1"
characters:
dependency: transitive
description:
name: characters
url: "https://pub.flutter-io.cn"
source: hosted
- version: "1.1.0-nullsafety.2"
+ version: "1.1.0-nullsafety.3"
charcode:
dependency: transitive
description:
name: charcode
url: "https://pub.flutter-io.cn"
source: hosted
- version: "1.2.0-nullsafety"
+ version: "1.2.0-nullsafety.1"
clock:
dependency: transitive
description:
name: clock
url: "https://pub.flutter-io.cn"
source: hosted
- version: "1.1.0-nullsafety"
+ version: "1.1.0-nullsafety.1"
collection:
dependency: transitive
description:
name: collection
url: "https://pub.flutter-io.cn"
source: hosted
- version: "1.15.0-nullsafety.2"
+ version: "1.15.0-nullsafety.3"
dio:
dependency: "direct main"
description:
@@ -56,7 +56,7 @@ packages:
name: fake_async
url: "https://pub.flutter-io.cn"
source: hosted
- version: "1.1.0-nullsafety"
+ version: "1.2.0-nullsafety.1"
ffi:
dependency: transitive
description:
@@ -82,7 +82,7 @@ packages:
path: ".."
relative: true
source: path
- version: "0.1.1"
+ version: "0.1.2"
flutter_test:
dependency: "direct dev"
description: flutter
@@ -108,21 +108,21 @@ packages:
name: matcher
url: "https://pub.flutter-io.cn"
source: hosted
- version: "0.12.10-nullsafety"
+ version: "0.12.10-nullsafety.1"
meta:
dependency: transitive
description:
name: meta
url: "https://pub.flutter-io.cn"
source: hosted
- version: "1.3.0-nullsafety.2"
+ version: "1.3.0-nullsafety.3"
path:
dependency: transitive
description:
name: path
url: "https://pub.flutter-io.cn"
source: hosted
- version: "1.8.0-nullsafety"
+ version: "1.8.0-nullsafety.1"
sky_engine:
dependency: transitive
description: flutter
@@ -134,56 +134,56 @@ packages:
name: source_span
url: "https://pub.flutter-io.cn"
source: hosted
- version: "1.8.0-nullsafety"
+ version: "1.8.0-nullsafety.2"
stack_trace:
dependency: transitive
description:
name: stack_trace
url: "https://pub.flutter-io.cn"
source: hosted
- version: "1.10.0-nullsafety"
+ version: "1.10.0-nullsafety.2"
stream_channel:
dependency: transitive
description:
name: stream_channel
url: "https://pub.flutter-io.cn"
source: hosted
- version: "2.1.0-nullsafety"
+ version: "2.1.0-nullsafety.1"
string_scanner:
dependency: transitive
description:
name: string_scanner
url: "https://pub.flutter-io.cn"
source: hosted
- version: "1.1.0-nullsafety"
+ version: "1.1.0-nullsafety.1"
term_glyph:
dependency: transitive
description:
name: term_glyph
url: "https://pub.flutter-io.cn"
source: hosted
- version: "1.2.0-nullsafety"
+ version: "1.2.0-nullsafety.1"
test_api:
dependency: transitive
description:
name: test_api
url: "https://pub.flutter-io.cn"
source: hosted
- version: "0.2.19-nullsafety"
+ version: "0.2.19-nullsafety.2"
typed_data:
dependency: transitive
description:
name: typed_data
url: "https://pub.flutter-io.cn"
source: hosted
- version: "1.3.0-nullsafety.2"
+ version: "1.3.0-nullsafety.3"
vector_math:
dependency: transitive
description:
name: vector_math
url: "https://pub.flutter-io.cn"
source: hosted
- version: "2.1.0-nullsafety.2"
+ version: "2.1.0-nullsafety.3"
sdks:
- dart: ">=2.10.0-0.0.dev <2.10.0"
+ dart: ">=2.10.0-110 <=2.11.0-181.0.dev"
flutter: ">=1.20.0 <2.0.0"
diff --git a/lib/ffi.dart b/lib/ffi.dart
index 7b15966..89677fb 100644
--- a/lib/ffi.dart
+++ b/lib/ffi.dart
@@ -3,7 +3,7 @@
* @Author: ekibun
* @Date: 2020-09-19 10:29:04
* @LastEditors: ekibun
- * @LastEditTime: 2020-09-27 01:12:16
+ * @LastEditTime: 2020-10-03 23:27:15
*/
import 'dart:ffi';
import 'dart:io';
@@ -744,3 +744,16 @@ final Pointer Function(
Pointer,
)>>("jsNewPromiseCapability")
.asFunction();
+
+/// void jsFree(JSContext *ctx, void *ptab)
+final void Function(
+ Pointer ctx,
+ Pointer ptab,
+) jsFree = qjsLib
+ .lookup<
+ NativeFunction<
+ Void Function(
+ Pointer,
+ Pointer,
+ )>>("jsFree")
+ .asFunction();
diff --git a/lib/flutter_qjs.dart b/lib/flutter_qjs.dart
index 4af1c82..ddc0ac8 100644
--- a/lib/flutter_qjs.dart
+++ b/lib/flutter_qjs.dart
@@ -3,7 +3,7 @@
* @Author: ekibun
* @Date: 2020-08-08 08:29:09
* @LastEditors: ekibun
- * @LastEditTime: 2020-10-03 00:18:49
+ * @LastEditTime: 2020-10-03 21:55:07
*/
import 'dart:async';
import 'dart:ffi';
@@ -116,9 +116,10 @@ class FlutterQjs {
}
/// Evaluate js script.
- Future evaluate(String command, String name) async {
+ Future evaluate(String command, {String name, int evalFlags}) async {
_ensureEngine();
- var jsval = jsEval(_ctx, command, name, JSEvalType.GLOBAL);
+ var jsval =
+ jsEval(_ctx, command, name ?? "", evalFlags ?? JSEvalType.GLOBAL);
if (jsIsException(jsval) != 0) {
throw Exception(parseJSException(_ctx));
}
diff --git a/lib/isolate.dart b/lib/isolate.dart
index 38cc760..3b7459d 100644
--- a/lib/isolate.dart
+++ b/lib/isolate.dart
@@ -3,7 +3,7 @@
* @Author: ekibun
* @Date: 2020-10-02 13:49:03
* @LastEditors: ekibun
- * @LastEditTime: 2020-10-03 00:18:40
+ * @LastEditTime: 2020-10-03 22:21:31
*/
import 'dart:async';
import 'dart:ffi';
@@ -123,18 +123,21 @@ void _runJsIsolate(Map spawnMessage) async {
sendPort.send(port.sendPort);
qjs.setMethodHandler(methodHandler);
qjs.setModuleHandler((name) {
- var ptr = allocate();
+ var ptr = allocate>();
+ ptr.value = Pointer.fromAddress(0);
sendPort.send({
'type': 'module',
'name': name,
'ptr': ptr.address,
});
- ptr.value = 0;
- while (ptr.value == 0) sleep(Duration.zero);
- print(ptr.value);
- if (ptr.value == -1) throw Exception("Module Not found");
- var strptr = Pointer.fromAddress(ptr.value);
- var ret = Utf8.fromUtf8(strptr);
+ while (ptr.value.address == 0) sleep(Duration.zero);
+ if (ptr.value.address == -1) throw Exception("Module Not found");
+ var ret = Utf8.fromUtf8(ptr.value);
+ sendPort.send({
+ 'type': 'release',
+ 'ptr': ptr.value.address,
+ });
+ free(ptr);
return ret;
});
qjs.dispatch();
@@ -144,7 +147,11 @@ void _runJsIsolate(Map spawnMessage) async {
try {
switch (msg['type']) {
case 'evaluate':
- data = await qjs.evaluate(msg['command'], msg['name']);
+ data = await qjs.evaluate(
+ msg['command'],
+ name: msg['name'],
+ evalFlags: msg['flag'],
+ );
break;
case 'call':
data = JSFunction.fromAddress(
@@ -174,7 +181,7 @@ typedef JsAsyncModuleHandler = Future Function(String name);
typedef JsIsolateSpawn = void Function(SendPort sendPort);
class IsolateQjs {
- SendPort _sendPort;
+ Future _sendPort;
JsMethodHandler _methodHandler;
JsAsyncModuleHandler _moduleHandler;
@@ -182,7 +189,7 @@ class IsolateQjs {
/// The function must be a top-level function or a static method
IsolateQjs(this._methodHandler);
- Future _ensureEngine() async {
+ _ensureEngine() {
if (_sendPort != null) return;
ReceivePort port = ReceivePort();
Isolate.spawn(
@@ -193,28 +200,30 @@ class IsolateQjs {
},
errorsAreFatal: true,
);
- var completer = Completer();
+ var completer = Completer();
port.listen((msg) async {
if (msg is SendPort && !completer.isCompleted) {
- _sendPort = msg;
- completer.complete();
+ completer.complete(msg);
return;
}
switch (msg['type']) {
case 'module':
- var ptr = Pointer.fromAddress(msg['ptr']);
+ var ptr = Pointer.fromAddress(msg['ptr']);
try {
- ptr.value = Utf8.toUtf8(await _moduleHandler(msg['name'])).address;
+ ptr.value = Utf8.toUtf8(await _moduleHandler(msg['name']));
} catch (e) {
- ptr.value = -1;
+ ptr.value = Pointer.fromAddress(-1);
}
break;
+ case 'release':
+ free(Pointer.fromAddress(msg['ptr']));
+ break;
}
}, onDone: () {
close();
if (!completer.isCompleted) completer.completeError('isolate close');
});
- await completer.future;
+ _sendPort = completer.future;
}
/// Set a handler to manage js module.
@@ -223,24 +232,29 @@ class IsolateQjs {
}
close() {
- _sendPort.send({
- 'type': 'close',
+ if (_sendPort == null) return;
+ _sendPort.then((sendPort) {
+ sendPort.send({
+ 'type': 'close',
+ });
});
_sendPort = null;
}
- Future evaluate(String command, String name) async {
- await _ensureEngine();
+ Future evaluate(String command, {String name, int evalFlags}) async {
+ _ensureEngine();
var evaluatePort = ReceivePort();
- _sendPort.send({
+ var sendPort = await _sendPort;
+ sendPort.send({
'type': 'evaluate',
'command': command,
'name': name,
+ 'flag': evalFlags,
'port': evaluatePort.sendPort,
});
var result = await evaluatePort.first;
if (result['data'] != null)
- return _decodeData(result['data'], _sendPort);
+ return _decodeData(result['data'], sendPort);
else
throw result['error'];
}
diff --git a/lib/wrapper.dart b/lib/wrapper.dart
index 1a98f8d..923b1d1 100644
--- a/lib/wrapper.dart
+++ b/lib/wrapper.dart
@@ -3,7 +3,7 @@
* @Author: ekibun
* @Date: 2020-09-19 22:07:47
* @LastEditors: ekibun
- * @LastEditTime: 2020-10-02 16:37:16
+ * @LastEditTime: 2020-10-03 23:27:36
*/
import 'dart:async';
import 'dart:ffi';
@@ -84,11 +84,12 @@ class JSFunction extends JSRefValue {
jsFreeValue(ctx, jsArg);
}
bool isException = jsIsException(jsRet) != 0;
- var ret = jsToDart(ctx, jsRet);
- jsFreeValue(ctx, jsRet);
if (isException) {
+ jsFreeValue(ctx, jsRet);
throw Exception(parseJSException(ctx));
}
+ var ret = jsToDart(ctx, jsRet);
+ jsFreeValue(ctx, jsRet);
return ret;
}
@@ -257,6 +258,7 @@ dynamic jsToDart(Pointer ctx, Pointer val, {Map cache}) {
jsFreeValue(ctx, jsProp);
jsFreeAtom(ctx, jsAtom);
}
+ jsFree(ctx, ptab.value);
free(ptab);
return ret;
}
diff --git a/pubspec.lock b/pubspec.lock
index b68689c..65c7e4f 100644
--- a/pubspec.lock
+++ b/pubspec.lock
@@ -7,49 +7,49 @@ packages:
name: async
url: "https://pub.flutter-io.cn"
source: hosted
- version: "2.5.0-nullsafety"
+ version: "2.5.0-nullsafety.1"
boolean_selector:
dependency: transitive
description:
name: boolean_selector
url: "https://pub.flutter-io.cn"
source: hosted
- version: "2.1.0-nullsafety"
+ version: "2.1.0-nullsafety.1"
characters:
dependency: transitive
description:
name: characters
url: "https://pub.flutter-io.cn"
source: hosted
- version: "1.1.0-nullsafety.2"
+ version: "1.1.0-nullsafety.3"
charcode:
dependency: transitive
description:
name: charcode
url: "https://pub.flutter-io.cn"
source: hosted
- version: "1.2.0-nullsafety"
+ version: "1.2.0-nullsafety.1"
clock:
dependency: transitive
description:
name: clock
url: "https://pub.flutter-io.cn"
source: hosted
- version: "1.1.0-nullsafety"
+ version: "1.1.0-nullsafety.1"
collection:
dependency: transitive
description:
name: collection
url: "https://pub.flutter-io.cn"
source: hosted
- version: "1.15.0-nullsafety.2"
+ version: "1.15.0-nullsafety.3"
fake_async:
dependency: transitive
description:
name: fake_async
url: "https://pub.flutter-io.cn"
source: hosted
- version: "1.1.0-nullsafety"
+ version: "1.2.0-nullsafety.1"
ffi:
dependency: "direct main"
description:
@@ -73,21 +73,21 @@ packages:
name: matcher
url: "https://pub.flutter-io.cn"
source: hosted
- version: "0.12.10-nullsafety"
+ version: "0.12.10-nullsafety.1"
meta:
dependency: transitive
description:
name: meta
url: "https://pub.flutter-io.cn"
source: hosted
- version: "1.3.0-nullsafety.2"
+ version: "1.3.0-nullsafety.3"
path:
dependency: transitive
description:
name: path
url: "https://pub.flutter-io.cn"
source: hosted
- version: "1.8.0-nullsafety"
+ version: "1.8.0-nullsafety.1"
sky_engine:
dependency: transitive
description: flutter
@@ -99,56 +99,56 @@ packages:
name: source_span
url: "https://pub.flutter-io.cn"
source: hosted
- version: "1.8.0-nullsafety"
+ version: "1.8.0-nullsafety.2"
stack_trace:
dependency: transitive
description:
name: stack_trace
url: "https://pub.flutter-io.cn"
source: hosted
- version: "1.10.0-nullsafety"
+ version: "1.10.0-nullsafety.2"
stream_channel:
dependency: transitive
description:
name: stream_channel
url: "https://pub.flutter-io.cn"
source: hosted
- version: "2.1.0-nullsafety"
+ version: "2.1.0-nullsafety.1"
string_scanner:
dependency: transitive
description:
name: string_scanner
url: "https://pub.flutter-io.cn"
source: hosted
- version: "1.1.0-nullsafety"
+ version: "1.1.0-nullsafety.1"
term_glyph:
dependency: transitive
description:
name: term_glyph
url: "https://pub.flutter-io.cn"
source: hosted
- version: "1.2.0-nullsafety"
+ version: "1.2.0-nullsafety.1"
test_api:
dependency: transitive
description:
name: test_api
url: "https://pub.flutter-io.cn"
source: hosted
- version: "0.2.19-nullsafety"
+ version: "0.2.19-nullsafety.2"
typed_data:
dependency: transitive
description:
name: typed_data
url: "https://pub.flutter-io.cn"
source: hosted
- version: "1.3.0-nullsafety.2"
+ version: "1.3.0-nullsafety.3"
vector_math:
dependency: transitive
description:
name: vector_math
url: "https://pub.flutter-io.cn"
source: hosted
- version: "2.1.0-nullsafety.2"
+ version: "2.1.0-nullsafety.3"
sdks:
- dart: ">=2.10.0-0.0.dev <2.10.0"
+ dart: ">=2.10.0-110 <=2.11.0-181.0.dev"
flutter: ">=1.20.0 <2.0.0"
diff --git a/pubspec.yaml b/pubspec.yaml
index be064db..0537289 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,6 +1,6 @@
name: flutter_qjs
description: This plugin is a simple js engine for flutter using the `quickjs` project. Plugin currently supports all the platforms except web!
-version: 0.1.1
+version: 0.1.2
homepage: https://github.com/ekibun/flutter_qjs
environment:
diff --git a/test/flutter_qjs_test.dart b/test/flutter_qjs_test.dart
index ebeb76f..ef72181 100644
--- a/test/flutter_qjs_test.dart
+++ b/test/flutter_qjs_test.dart
@@ -3,7 +3,7 @@
* @Author: ekibun
* @Date: 2020-09-06 13:02:46
* @LastEditors: ekibun
- * @LastEditTime: 2020-10-02 17:27:52
+ * @LastEditTime: 2020-10-03 21:36:06
*/
import 'dart:convert';
import 'dart:io';
@@ -67,7 +67,7 @@ void main() async {
(...args)=>`hello \${args}!`, a,
0.1, true, false, 1, "world", module
]));
- """, "");
+ """, name: "");
print(value);
print(await value[0]('world'));
qjs.close();