After upgrading to V8 8.6.337, with a 20 MB heap limit, the
near-heap-limit callback never gets called before V8 runs out of memory.
It turns out that this test exhibits memory allocation behavior which
produces so little actual garbage that 'scavenge' type garbage
collections make memory usage go up rather than down. Because of this,
V8 runs out of memory in the middle of a garbage collection cycle, after
it has already decided that there's no need to run the near-heap-limit
callback.
The issue is fixed by making sure that some actual garbage is produced
alongside with the retained objects that will eventually fill up the
heap.
* Merged all handle type implementations into one file ('handle.h').
* Made it so that `Global` handles cannot be empty.
* Renamed the `AsHandle` trait to `Handle`, and made it more generally
useful.
* Simplified how `PartialEq` is implemented for V8 heap objects and/or
the `Local`/`Global` handles that reference them.
Local handles never need to be mutable. This patch also rounds up the
last few places where we were still asking the user to pass an `&mut T`
to an API method.
According to v8.h, "the returned handle is valid until this TryCatch
block has been destroyed". This is incorrect, as can be demonstrated
with the test below. In practice the return value lives no longer and
no shorter than the active HandleScope at the time these methods are
called. An issue has been opened about this in the V8 bug tracker:
https://bugs.chromium.org/p/v8/issues/detail?id=10537.
```rust
fn try_catch_bad_lifetimes() {
let _setup_guard = setup();
let mut isolate = v8::Isolate::new(Default::default());
let mut hs = v8::HandleScope::new(&mut isolate);
let scope = hs.enter();
let context = v8::Context::new(scope);
let mut cs = v8::ContextScope::new(scope, context);
let scope = cs.enter();
let caught_msg_2 = {
let mut try_catch = v8::TryCatch::new(scope);
let try_catch = try_catch.enter();
let caught_msg_1 = {
let mut hs = v8::HandleScope::new(scope);
let scope = hs.enter();
// Throw exception #1.
let msg_1 = v8::String::new(scope, "BOOM!").unwrap();
let exc_1 = v8::Exception::type_error(scope, msg_1);
scope.isolate().throw_exception(exc_1);
// Catch exception #1.
let caught_msg_1 = try_catch.message().unwrap();
let caught_str_1 =
caught_msg_1.get(scope).to_rust_string_lossy(scope);
assert!(caught_str_1.contains("BOOM"));
// Move `caught_msg_1` out of the HandleScope it was created in.
// The borrow checker allows this because `caught_msg_1`'s
// lifetime is contrained to not outlive the TryCatch, but it is
// allowed to outlive the HandleScope that was active when the
// exception was caught.
caught_msg_1
};
// Next line crashes.
let caught_str_1 =
caught_msg_1.get(scope).to_rust_string_lossy(scope);
assert!(caught_str_1.contains("BOOM"));
// Throws exception #2.
let msg_2 = v8::String::new(scope, "DANG!").unwrap();
let exc_2 = v8::Exception::type_error(scope, msg_2);
scope.isolate().throw_exception(exc_2);
// Catch exception #2.
let caught_msg_2 = try_catch.message().unwrap();
let caught_str_2 =
caught_msg_2.get(scope).to_rust_string_lossy(scope);
assert!(caught_str_2.contains("DANG"));
// Move `caught_msg_2` out of the extent of the TryCatch, but still
// within the extent of its HandleScope. This is unnecessarily
// rejected at compile time.
caught_msg_2
};
let caught_str_2 =
caught_msg_2.get(scope).to_rust_string_lossy(scope);
assert!(caught_str_2.contains("DANG"));
}
```
* Add `SharedPtr` as a nullable sibling to `SharedRef`.
* Add `Borrow`, `AsRef` and `AsMut` implementations as appropriate.
* `SharedRef<T>` now derefs to `T` rather than to `UnsafeCell<T>`.
* `array_buffer::BackingStore` now derefs to `[Cell<u8>]`.
This doesn't really follow the current V8 API (it's pretty close to how
V8 used to be back in 2012 though.) However:
1. The C++ API is very C++-y and doesn't carry over well to Rust, and
2. It addresses the immediate need of being able to take heap snapshots.
Refs #298
The `get_current_context()` and `get_entered_or_microtask_context()`
methods now return `Option<Local<Context>>` to reflect that an isolate
may not have entered any context.
They're also moved from `Isolate` to `struct Entered` because it turns
out that the underlying V8 API calls actually create new local handles,
hence they should only be used inside an active HandleScope.
The `InContext` trait has been removed.
A test exercising `ContextScope` and the `get_*_context()` methods
mentioned above was added.
Closes: #248.
This patch clarifies that v8::Isolate is a single threaded creature,
which can only be accessed from other threads in special circumstances.
To ensure optimal operation in Deno, we remove v8::Locker, which ought
to be unnecessary when a thread is dedicated to each Isolate and the
Isolates never move between threads.
There are valid use-cases for v8::Locker, and we hope to address them in
future versions of rusty_v8.
Co-authored-by: Bert Belder <bertbelder@gmail.com>
* Rename `Object::new2()` to `Object::with_prototype_and_properties()`.
* Make `Object::with_prototype_and_properties()` take a slice of keys
and a slice of values as arguments, instead of using
`Vec<v8::Local<v8::Name>>` and `Vec<v8::Local<v8::Value>>>`.
* Remove type `MaybeBool` from the public interface. These methods now
return `Option<bool>` instead.
* Fix parameter type mismatches between Rust and C++ APIs.
* The purpose of this change is to match the C++ API more closely.
* This patch also increases consistency between the 'extern "C"'
function definitions on the Rust side with those on the C++ side.
* The 'message' parameter (a v8::String) to the various error
constructors no longer needs to be mutable.