Make the Isolate parameter mandatory in Locker and Unlocker classes.

Note that leaving out the Isolate parameter previously had a very special
meaning, namely "use the *default* Isolate", i.e. the one magically created at
program initialization time. All other API entries use the meaning "current
Isolate", which is different in a multi-threaded setting and confusing.

Temporarily disabled deprecations until Chrome is ready.

BUG=v8:2487

Review URL: https://codereview.chromium.org/11970009

git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@13419 ce2b1a6d-e550-0410-aec6-3dcde31c8c00
parent ca78326b
...@@ -38,6 +38,9 @@ ...@@ -38,6 +38,9 @@
#ifndef V8_H_ #ifndef V8_H_
#define V8_H_ #define V8_H_
// TODO(svenpanne) Remove me when the Chrome bindings are adapted.
#define V8_DISABLE_DEPRECATIONS 1
#include "v8stdint.h" #include "v8stdint.h"
#ifdef _WIN32 #ifdef _WIN32
...@@ -3879,21 +3882,19 @@ class V8EXPORT Context { ...@@ -3879,21 +3882,19 @@ class V8EXPORT Context {
/** /**
* Multiple threads in V8 are allowed, but only one thread at a time * Multiple threads in V8 are allowed, but only one thread at a time is allowed
* is allowed to use any given V8 isolate. See Isolate class * to use any given V8 isolate, see the comments in the Isolate class. The
* comments. The definition of 'using V8 isolate' includes * definition of 'using a V8 isolate' includes accessing handles or holding onto
* accessing handles or holding onto object pointers obtained * object pointers obtained from V8 handles while in the particular V8 isolate.
* from V8 handles while in the particular V8 isolate. It is up * It is up to the user of V8 to ensure, perhaps with locking, that this
* to the user of V8 to ensure (perhaps with locking) that this * constraint is not violated. In addition to any other synchronization
* constraint is not violated. In addition to any other synchronization * mechanism that may be used, the v8::Locker and v8::Unlocker classes must be
* mechanism that may be used, the v8::Locker and v8::Unlocker classes * used to signal thead switches to V8.
* must be used to signal thead switches to V8.
* *
* v8::Locker is a scoped lock object. While it's * v8::Locker is a scoped lock object. While it's active, i.e. between its
* active (i.e. between its construction and destruction) the current thread is * construction and destruction, the current thread is allowed to use the locked
* allowed to use the locked isolate. V8 guarantees that an isolate can be * isolate. V8 guarantees that an isolate can be locked by at most one thread at
* locked by at most one thread at any time. In other words, the scope of a * any time. In other words, the scope of a v8::Locker is a critical section.
* v8::Locker is a critical section.
* *
* Sample usage: * Sample usage:
* \code * \code
...@@ -3907,9 +3908,9 @@ class V8EXPORT Context { ...@@ -3907,9 +3908,9 @@ class V8EXPORT Context {
* } // Destructor called here * } // Destructor called here
* \endcode * \endcode
* *
* If you wish to stop using V8 in a thread A you can do this either * If you wish to stop using V8 in a thread A you can do this either by
* by destroying the v8::Locker object as above or by constructing a * destroying the v8::Locker object as above or by constructing a v8::Unlocker
* v8::Unlocker object: * object:
* *
* \code * \code
* { * {
...@@ -3922,19 +3923,17 @@ class V8EXPORT Context { ...@@ -3922,19 +3923,17 @@ class V8EXPORT Context {
* isolate->Enter(); * isolate->Enter();
* \endcode * \endcode
* *
* The Unlocker object is intended for use in a long-running callback * The Unlocker object is intended for use in a long-running callback from V8,
* from V8, where you want to release the V8 lock for other threads to * where you want to release the V8 lock for other threads to use.
* use.
* *
* The v8::Locker is a recursive lock. That is, you can lock more than * The v8::Locker is a recursive lock, i.e. you can lock more than once in a
* once in a given thread. This can be useful if you have code that can * given thread. This can be useful if you have code that can be called either
* be called either from code that holds the lock or from code that does * from code that holds the lock or from code that does not. The Unlocker is
* not. The Unlocker is not recursive so you can not have several * not recursive so you can not have several Unlockers on the stack at once, and
* Unlockers on the stack at once, and you can not use an Unlocker in a * you can not use an Unlocker in a thread that is not inside a Locker's scope.
* thread that is not inside a Locker's scope.
* *
* An unlocker will unlock several lockers if it has to and reinstate * An unlocker will unlock several lockers if it has to and reinstate the
* the correct depth of locking on its destruction. eg.: * correct depth of locking on its destruction, e.g.:
* *
* \code * \code
* // V8 not locked. * // V8 not locked.
...@@ -3957,17 +3956,23 @@ class V8EXPORT Context { ...@@ -3957,17 +3956,23 @@ class V8EXPORT Context {
* } * }
* // V8 Now no longer locked. * // V8 Now no longer locked.
* \endcode * \endcode
*
*
*/ */
class V8EXPORT Unlocker { class V8EXPORT Unlocker {
public: public:
/** /**
* Initialize Unlocker for a given Isolate. NULL means default isolate. * Initialize Unlocker for a given Isolate.
*/ */
explicit Unlocker(Isolate* isolate = NULL); V8_INLINE(explicit Unlocker(Isolate* isolate)) { Initialize(isolate); }
/**
* Deprecated. Use Isolate version instead.
*/
V8_DEPRECATED(Unlocker());
~Unlocker(); ~Unlocker();
private: private:
void Initialize(Isolate* isolate);
internal::Isolate* isolate_; internal::Isolate* isolate_;
}; };
...@@ -3975,9 +3980,15 @@ class V8EXPORT Unlocker { ...@@ -3975,9 +3980,15 @@ class V8EXPORT Unlocker {
class V8EXPORT Locker { class V8EXPORT Locker {
public: public:
/** /**
* Initialize Locker for a given Isolate. NULL means default isolate. * Initialize Locker for a given Isolate.
*/
V8_INLINE(explicit Locker(Isolate* isolate)) { Initialize(isolate); }
/**
* Deprecated. Use Isolate version instead.
*/ */
explicit Locker(Isolate* isolate = NULL); V8_DEPRECATED(Locker());
~Locker(); ~Locker();
/** /**
...@@ -3995,10 +4006,10 @@ class V8EXPORT Locker { ...@@ -3995,10 +4006,10 @@ class V8EXPORT Locker {
static void StopPreemption(); static void StopPreemption();
/** /**
* Returns whether or not the locker for a given isolate, or default isolate * Returns whether or not the locker for a given isolate, is locked by the
* if NULL is given, is locked by the current thread. * current thread.
*/ */
static bool IsLocked(Isolate* isolate = NULL); static bool IsLocked(Isolate* isolate);
/** /**
* Returns whether v8::Locker is being used by this V8 instance. * Returns whether v8::Locker is being used by this V8 instance.
...@@ -4006,6 +4017,8 @@ class V8EXPORT Locker { ...@@ -4006,6 +4017,8 @@ class V8EXPORT Locker {
static bool IsActive(); static bool IsActive();
private: private:
void Initialize(Isolate* isolate);
bool has_lock_; bool has_lock_;
bool top_level_; bool top_level_;
internal::Isolate* isolate_; internal::Isolate* isolate_;
......
...@@ -214,7 +214,7 @@ int RunMain(int argc, char* argv[]) { ...@@ -214,7 +214,7 @@ int RunMain(int argc, char* argv[]) {
#ifdef ENABLE_DEBUGGER_SUPPORT #ifdef ENABLE_DEBUGGER_SUPPORT
debug_message_context = v8::Persistent<v8::Context>::New(context); debug_message_context = v8::Persistent<v8::Context>::New(context);
v8::Locker locker; v8::Locker locker(v8::Isolate::GetCurrent());
if (support_callback) { if (support_callback) {
v8::Debug::SetDebugMessageDispatchHandler(DispatchDebugMessages, true); v8::Debug::SetDebugMessageDispatchHandler(DispatchDebugMessages, true);
...@@ -265,7 +265,7 @@ int RunMain(int argc, char* argv[]) { ...@@ -265,7 +265,7 @@ int RunMain(int argc, char* argv[]) {
bool RunCppCycle(v8::Handle<v8::Script> script, v8::Local<v8::Context> context, bool RunCppCycle(v8::Handle<v8::Script> script, v8::Local<v8::Context> context,
bool report_exceptions) { bool report_exceptions) {
#ifdef ENABLE_DEBUGGER_SUPPORT #ifdef ENABLE_DEBUGGER_SUPPORT
v8::Locker lock; v8::Locker lock(v8::Isolate::GetCurrent());
#endif // ENABLE_DEBUGGER_SUPPORT #endif // ENABLE_DEBUGGER_SUPPORT
v8::Handle<v8::String> fun_name = v8::String::New("ProcessLine"); v8::Handle<v8::String> fun_name = v8::String::New("ProcessLine");
...@@ -420,7 +420,7 @@ v8::Handle<v8::String> ReadLine() { ...@@ -420,7 +420,7 @@ v8::Handle<v8::String> ReadLine() {
char* res; char* res;
{ {
#ifdef ENABLE_DEBUGGER_SUPPORT #ifdef ENABLE_DEBUGGER_SUPPORT
v8::Unlocker unlocker; v8::Unlocker unlocker(v8::Isolate::GetCurrent());
#endif // ENABLE_DEBUGGER_SUPPORT #endif // ENABLE_DEBUGGER_SUPPORT
res = fgets(buffer, kBufferSize, stdin); res = fgets(buffer, kBufferSize, stdin);
} }
......
...@@ -273,7 +273,7 @@ RemoteDebuggerEvent* RemoteDebugger::GetEvent() { ...@@ -273,7 +273,7 @@ RemoteDebuggerEvent* RemoteDebugger::GetEvent() {
void RemoteDebugger::HandleMessageReceived(char* message) { void RemoteDebugger::HandleMessageReceived(char* message) {
Locker lock; Locker lock(v8::Isolate::GetCurrent());
HandleScope scope; HandleScope scope;
// Print the event details. // Print the event details.
...@@ -302,7 +302,7 @@ void RemoteDebugger::HandleMessageReceived(char* message) { ...@@ -302,7 +302,7 @@ void RemoteDebugger::HandleMessageReceived(char* message) {
void RemoteDebugger::HandleKeyboardCommand(char* command) { void RemoteDebugger::HandleKeyboardCommand(char* command) {
Locker lock; Locker lock(v8::Isolate::GetCurrent());
HandleScope scope; HandleScope scope;
// Convert the debugger command to a JSON debugger request. // Convert the debugger command to a JSON debugger request.
......
...@@ -886,7 +886,7 @@ Handle<Value> Shell::Uint8ClampedArray(const Arguments& args) { ...@@ -886,7 +886,7 @@ Handle<Value> Shell::Uint8ClampedArray(const Arguments& args) {
Handle<Value> Shell::Yield(const Arguments& args) { Handle<Value> Shell::Yield(const Arguments& args) {
v8::Unlocker unlocker; v8::Unlocker unlocker(args.GetIsolate());
return Undefined(); return Undefined();
} }
...@@ -1093,8 +1093,8 @@ void Shell::AddHistogramSample(void* histogram, int sample) { ...@@ -1093,8 +1093,8 @@ void Shell::AddHistogramSample(void* histogram, int sample) {
} }
void Shell::InstallUtilityScript() { void Shell::InstallUtilityScript(Isolate* isolate) {
Locker lock; Locker lock(isolate);
HandleScope scope; HandleScope scope;
// If we use the utility context, we have to set the security tokens so that // If we use the utility context, we have to set the security tokens so that
// utility, evaluation and debug context can all access each other. // utility, evaluation and debug context can all access each other.
...@@ -1272,7 +1272,7 @@ void Shell::Initialize(Isolate* isolate) { ...@@ -1272,7 +1272,7 @@ void Shell::Initialize(Isolate* isolate) {
void Shell::InitializeDebugger(Isolate* isolate) { void Shell::InitializeDebugger(Isolate* isolate) {
if (options.test_shell) return; if (options.test_shell) return;
#ifndef V8_SHARED #ifndef V8_SHARED
Locker lock; Locker lock(isolate);
HandleScope scope; HandleScope scope;
Handle<ObjectTemplate> global_template = CreateGlobalTemplate(isolate); Handle<ObjectTemplate> global_template = CreateGlobalTemplate(isolate);
utility_context_ = Context::New(NULL, global_template); utility_context_ = Context::New(NULL, global_template);
...@@ -1490,7 +1490,7 @@ Handle<String> Shell::ReadFile(Isolate* isolate, const char* name) { ...@@ -1490,7 +1490,7 @@ Handle<String> Shell::ReadFile(Isolate* isolate, const char* name) {
void Shell::RunShell(Isolate* isolate) { void Shell::RunShell(Isolate* isolate) {
Locker locker; Locker locker(isolate);
Context::Scope context_scope(evaluation_context_); Context::Scope context_scope(evaluation_context_);
HandleScope outer_scope; HandleScope outer_scope;
Handle<String> name = String::New("(d8)"); Handle<String> name = String::New("(d8)");
...@@ -1540,7 +1540,7 @@ void ShellThread::Run() { ...@@ -1540,7 +1540,7 @@ void ShellThread::Run() {
} }
// Prepare the context for this thread. // Prepare the context for this thread.
Locker locker; Locker locker(isolate_);
HandleScope outer_scope; HandleScope outer_scope;
Persistent<Context> thread_context = Persistent<Context> thread_context =
Shell::CreateEvaluationContext(isolate_); Shell::CreateEvaluationContext(isolate_);
...@@ -1839,7 +1839,7 @@ int Shell::RunMain(Isolate* isolate, int argc, char* argv[]) { ...@@ -1839,7 +1839,7 @@ int Shell::RunMain(Isolate* isolate, int argc, char* argv[]) {
} }
#endif // V8_SHARED #endif // V8_SHARED
{ // NOLINT { // NOLINT
Locker lock; Locker lock(isolate);
HandleScope scope; HandleScope scope;
Persistent<Context> context = CreateEvaluationContext(isolate); Persistent<Context> context = CreateEvaluationContext(isolate);
if (options.last_run) { if (options.last_run) {
...@@ -1849,7 +1849,7 @@ int Shell::RunMain(Isolate* isolate, int argc, char* argv[]) { ...@@ -1849,7 +1849,7 @@ int Shell::RunMain(Isolate* isolate, int argc, char* argv[]) {
// If the interactive debugger is enabled make sure to activate // If the interactive debugger is enabled make sure to activate
// it before running the files passed on the command line. // it before running the files passed on the command line.
if (i::FLAG_debugger) { if (i::FLAG_debugger) {
InstallUtilityScript(); InstallUtilityScript(isolate);
} }
#endif // !V8_SHARED && ENABLE_DEBUGGER_SUPPORT #endif // !V8_SHARED && ENABLE_DEBUGGER_SUPPORT
} }
...@@ -1887,7 +1887,7 @@ int Shell::RunMain(Isolate* isolate, int argc, char* argv[]) { ...@@ -1887,7 +1887,7 @@ int Shell::RunMain(Isolate* isolate, int argc, char* argv[]) {
} }
if (threads.length() > 0 && options.use_preemption) { if (threads.length() > 0 && options.use_preemption) {
Locker lock; Locker lock(isolate);
Locker::StopPreemption(); Locker::StopPreemption();
} }
#endif // V8_SHARED #endif // V8_SHARED
...@@ -1934,7 +1934,7 @@ int Shell::Main(int argc, char* argv[]) { ...@@ -1934,7 +1934,7 @@ int Shell::Main(int argc, char* argv[]) {
#if !defined(V8_SHARED) && defined(ENABLE_DEBUGGER_SUPPORT) #if !defined(V8_SHARED) && defined(ENABLE_DEBUGGER_SUPPORT)
// Run remote debugger if requested, but never on --test // Run remote debugger if requested, but never on --test
if (i::FLAG_remote_debugger && !options.test_shell) { if (i::FLAG_remote_debugger && !options.test_shell) {
InstallUtilityScript(); InstallUtilityScript(isolate);
RunRemoteDebugger(i::FLAG_debugger_port); RunRemoteDebugger(i::FLAG_debugger_port);
return 0; return 0;
} }
...@@ -1947,7 +1947,7 @@ int Shell::Main(int argc, char* argv[]) { ...@@ -1947,7 +1947,7 @@ int Shell::Main(int argc, char* argv[]) {
&& !options.test_shell ) { && !options.test_shell ) {
#if !defined(V8_SHARED) && defined(ENABLE_DEBUGGER_SUPPORT) #if !defined(V8_SHARED) && defined(ENABLE_DEBUGGER_SUPPORT)
if (!i::FLAG_debugger) { if (!i::FLAG_debugger) {
InstallUtilityScript(); InstallUtilityScript(isolate);
} }
#endif // !V8_SHARED && ENABLE_DEBUGGER_SUPPORT #endif // !V8_SHARED && ENABLE_DEBUGGER_SUPPORT
RunShell(isolate); RunShell(isolate);
......
...@@ -381,7 +381,7 @@ class Shell : public i::AllStatic { ...@@ -381,7 +381,7 @@ class Shell : public i::AllStatic {
static i::Mutex* context_mutex_; static i::Mutex* context_mutex_;
static Counter* GetCounter(const char* name, bool is_histogram); static Counter* GetCounter(const char* name, bool is_histogram);
static void InstallUtilityScript(); static void InstallUtilityScript(Isolate* isolate);
#endif // V8_SHARED #endif // V8_SHARED
static void Initialize(Isolate* isolate); static void Initialize(Isolate* isolate);
static void InitializeDebugger(Isolate* isolate); static void InitializeDebugger(Isolate* isolate);
......
...@@ -3767,6 +3767,7 @@ void MessageDispatchHelperThread::Schedule() { ...@@ -3767,6 +3767,7 @@ void MessageDispatchHelperThread::Schedule() {
void MessageDispatchHelperThread::Run() { void MessageDispatchHelperThread::Run() {
Isolate* isolate = Isolate::Current();
while (true) { while (true) {
sem_->Wait(); sem_->Wait();
{ {
...@@ -3774,8 +3775,8 @@ void MessageDispatchHelperThread::Run() { ...@@ -3774,8 +3775,8 @@ void MessageDispatchHelperThread::Run() {
already_signalled_ = false; already_signalled_ = false;
} }
{ {
Locker locker; Locker locker(reinterpret_cast<v8::Isolate*>(isolate));
Isolate::Current()->debugger()->CallMessageDispatchHandler(); isolate->debugger()->CallMessageDispatchHandler();
} }
} }
} }
......
...@@ -408,9 +408,9 @@ void Isolate::EnterDefaultIsolate() { ...@@ -408,9 +408,9 @@ void Isolate::EnterDefaultIsolate() {
} }
Isolate* Isolate::GetDefaultIsolateForLocking() { v8::Isolate* Isolate::GetDefaultIsolateForLocking() {
EnsureDefaultIsolate(); EnsureDefaultIsolate();
return default_isolate_; return reinterpret_cast<v8::Isolate*>(default_isolate_);
} }
...@@ -1743,7 +1743,7 @@ void Isolate::Deinit() { ...@@ -1743,7 +1743,7 @@ void Isolate::Deinit() {
delete deoptimizer_data_; delete deoptimizer_data_;
deoptimizer_data_ = NULL; deoptimizer_data_ = NULL;
if (FLAG_preemption) { if (FLAG_preemption) {
v8::Locker locker; v8::Locker locker(reinterpret_cast<v8::Isolate*>(this));
v8::Locker::StopPreemption(); v8::Locker::StopPreemption();
} }
builtins_.TearDown(); builtins_.TearDown();
...@@ -2034,7 +2034,7 @@ bool Isolate::Init(Deserializer* des) { ...@@ -2034,7 +2034,7 @@ bool Isolate::Init(Deserializer* des) {
} }
if (FLAG_preemption) { if (FLAG_preemption) {
v8::Locker locker; v8::Locker locker(reinterpret_cast<v8::Isolate*>(this));
v8::Locker::StartPreemption(100); v8::Locker::StartPreemption(100);
} }
......
...@@ -1070,6 +1070,11 @@ class Isolate { ...@@ -1070,6 +1070,11 @@ class Isolate {
return &optimizing_compiler_thread_; return &optimizing_compiler_thread_;
} }
// PreInits and returns a default isolate. Needed when a new thread tries
// to create a Locker for the first time (the lock itself is in the isolate).
// TODO(svenpanne) This method is on death row...
static v8::Isolate* GetDefaultIsolateForLocking();
private: private:
Isolate(); Isolate();
...@@ -1153,10 +1158,6 @@ class Isolate { ...@@ -1153,10 +1158,6 @@ class Isolate {
// If one does not yet exist, allocate a new one. // If one does not yet exist, allocate a new one.
PerIsolateThreadData* FindOrAllocatePerThreadDataForThisThread(); PerIsolateThreadData* FindOrAllocatePerThreadDataForThisThread();
// PreInits and returns a default isolate. Needed when a new thread tries
// to create a Locker for the first time (the lock itself is in the isolate).
static Isolate* GetDefaultIsolateForLocking();
// Initializes the current thread to run this Isolate. // Initializes the current thread to run this Isolate.
// Not thread-safe. Multiple threads should not Enter/Exit the same isolate // Not thread-safe. Multiple threads should not Enter/Exit the same isolate
// at the same time, this should be prevented using external locking. // at the same time, this should be prevented using external locking.
......
...@@ -42,15 +42,18 @@ namespace v8 { ...@@ -42,15 +42,18 @@ namespace v8 {
bool Locker::active_ = false; bool Locker::active_ = false;
// Constructor for the Locker object. Once the Locker is constructed the Locker::Locker() {
// current thread will be guaranteed to have the lock for a given isolate. Initialize(i::Isolate::GetDefaultIsolateForLocking());
Locker::Locker(v8::Isolate* isolate) }
: has_lock_(false),
top_level_(true),
isolate_(reinterpret_cast<i::Isolate*>(isolate)) { // Once the Locker is initialized, the current thread will be guaranteed to have
if (isolate_ == NULL) { // the lock for a given isolate.
isolate_ = i::Isolate::GetDefaultIsolateForLocking(); void Locker::Initialize(v8::Isolate* isolate) {
} ASSERT(isolate != NULL);
has_lock_= false;
top_level_ = true;
isolate_ = reinterpret_cast<i::Isolate*>(isolate);
// Record that the Locker has been used at least once. // Record that the Locker has been used at least once.
active_ = true; active_ = true;
// Get the big lock if necessary. // Get the big lock if necessary.
...@@ -86,10 +89,8 @@ Locker::Locker(v8::Isolate* isolate) ...@@ -86,10 +89,8 @@ Locker::Locker(v8::Isolate* isolate)
bool Locker::IsLocked(v8::Isolate* isolate) { bool Locker::IsLocked(v8::Isolate* isolate) {
ASSERT(isolate != NULL);
i::Isolate* internal_isolate = reinterpret_cast<i::Isolate*>(isolate); i::Isolate* internal_isolate = reinterpret_cast<i::Isolate*>(isolate);
if (internal_isolate == NULL) {
internal_isolate = i::Isolate::GetDefaultIsolateForLocking();
}
return internal_isolate->thread_manager()->IsLockedByCurrentThread(); return internal_isolate->thread_manager()->IsLockedByCurrentThread();
} }
...@@ -115,11 +116,14 @@ Locker::~Locker() { ...@@ -115,11 +116,14 @@ Locker::~Locker() {
} }
Unlocker::Unlocker(v8::Isolate* isolate) Unlocker::Unlocker() {
: isolate_(reinterpret_cast<i::Isolate*>(isolate)) { Initialize(i::Isolate::GetDefaultIsolateForLocking());
if (isolate_ == NULL) { }
isolate_ = i::Isolate::GetDefaultIsolateForLocking();
}
void Unlocker::Initialize(v8::Isolate* isolate) {
ASSERT(isolate != NULL);
isolate_ = reinterpret_cast<i::Isolate*>(isolate);
ASSERT(isolate_->thread_manager()->IsLockedByCurrentThread()); ASSERT(isolate_->thread_manager()->IsLockedByCurrentThread());
if (isolate_->IsDefaultIsolate()) { if (isolate_->IsDefaultIsolate()) {
isolate_->Exit(); isolate_->Exit();
...@@ -479,7 +483,7 @@ void ContextSwitcher::Run() { ...@@ -479,7 +483,7 @@ void ContextSwitcher::Run() {
// Acknowledge the preemption by the receiving thread. // Acknowledge the preemption by the receiving thread.
void ContextSwitcher::PreemptionReceived() { void ContextSwitcher::PreemptionReceived() {
ASSERT(Locker::IsLocked()); ASSERT(Locker::IsLocked(i::Isolate::GetDefaultIsolateForLocking()));
// There is currently no accounting being done for this. But could be in the // There is currently no accounting being done for this. But could be in the
// future, which is why we leave this in. // future, which is why we leave this in.
} }
......
...@@ -69,8 +69,12 @@ static void PrintTestList(CcTest* current) { ...@@ -69,8 +69,12 @@ static void PrintTestList(CcTest* current) {
} }
v8::Isolate* CcTest::default_isolate_;
int main(int argc, char* argv[]) { int main(int argc, char* argv[]) {
v8::internal::FlagList::SetFlagsFromCommandLine(&argc, argv, true); v8::internal::FlagList::SetFlagsFromCommandLine(&argc, argv, true);
CcTest::set_default_isolate(v8::Isolate::GetCurrent());
CHECK(CcTest::default_isolate() != NULL);
int tests_run = 0; int tests_run = 0;
bool print_run_count = true; bool print_run_count = true;
for (int i = 1; i < argc; i++) { for (int i = 1; i < argc; i++) {
......
...@@ -57,13 +57,17 @@ class CcTest { ...@@ -57,13 +57,17 @@ class CcTest {
CcTest(TestFunction* callback, const char* file, const char* name, CcTest(TestFunction* callback, const char* file, const char* name,
const char* dependency, bool enabled); const char* dependency, bool enabled);
void Run() { callback_(); } void Run() { callback_(); }
static int test_count();
static CcTest* last() { return last_; } static CcTest* last() { return last_; }
CcTest* prev() { return prev_; } CcTest* prev() { return prev_; }
const char* file() { return file_; } const char* file() { return file_; }
const char* name() { return name_; } const char* name() { return name_; }
const char* dependency() { return dependency_; } const char* dependency() { return dependency_; }
bool enabled() { return enabled_; } bool enabled() { return enabled_; }
static void set_default_isolate(v8::Isolate* default_isolate) {
default_isolate_ = default_isolate;
}
static v8::Isolate* default_isolate() { return default_isolate_; }
private: private:
TestFunction* callback_; TestFunction* callback_;
const char* file_; const char* file_;
...@@ -72,6 +76,7 @@ class CcTest { ...@@ -72,6 +76,7 @@ class CcTest {
bool enabled_; bool enabled_;
static CcTest* last_; static CcTest* last_;
CcTest* prev_; CcTest* prev_;
static v8::Isolate* default_isolate_;
}; };
// Switches between all the Api tests using the threading support. // Switches between all the Api tests using the threading support.
...@@ -87,13 +92,6 @@ class CcTest { ...@@ -87,13 +92,6 @@ class CcTest {
class ApiTestFuzzer: public v8::internal::Thread { class ApiTestFuzzer: public v8::internal::Thread {
public: public:
void CallTest(); void CallTest();
explicit ApiTestFuzzer(int num)
: Thread("ApiTestFuzzer"),
test_number_(num),
gate_(v8::internal::OS::CreateSemaphore(0)),
active_(true) {
}
~ApiTestFuzzer() { delete gate_; }
// The ApiTestFuzzer is also a Thread, so it has a Run method. // The ApiTestFuzzer is also a Thread, so it has a Run method.
virtual void Run(); virtual void Run();
...@@ -112,6 +110,14 @@ class ApiTestFuzzer: public v8::internal::Thread { ...@@ -112,6 +110,14 @@ class ApiTestFuzzer: public v8::internal::Thread {
static void Fuzz(); static void Fuzz();
private: private:
explicit ApiTestFuzzer(int num)
: Thread("ApiTestFuzzer"),
test_number_(num),
gate_(v8::internal::OS::CreateSemaphore(0)),
active_(true) {
}
~ApiTestFuzzer() { delete gate_; }
static bool fuzzing_; static bool fuzzing_;
static int tests_being_run_; static int tests_being_run_;
static int current_; static int current_;
......
...@@ -11017,7 +11017,7 @@ void ApiTestFuzzer::Run() { ...@@ -11017,7 +11017,7 @@ void ApiTestFuzzer::Run() {
gate_->Wait(); gate_->Wait();
{ {
// ... get the V8 lock and start running the test. // ... get the V8 lock and start running the test.
v8::Locker locker; v8::Locker locker(CcTest::default_isolate());
CallTest(); CallTest();
} }
// This test finished. // This test finished.
...@@ -11081,7 +11081,7 @@ void ApiTestFuzzer::ContextSwitch() { ...@@ -11081,7 +11081,7 @@ void ApiTestFuzzer::ContextSwitch() {
// If the new thread is the same as the current thread there is nothing to do. // If the new thread is the same as the current thread there is nothing to do.
if (NextThread()) { if (NextThread()) {
// Now it can start. // Now it can start.
v8::Unlocker unlocker; v8::Unlocker unlocker(CcTest::default_isolate());
// Wait till someone starts us again. // Wait till someone starts us again.
gate_->Wait(); gate_->Wait();
// And we're off. // And we're off.
...@@ -11133,12 +11133,12 @@ void ApiTestFuzzer::CallTest() { ...@@ -11133,12 +11133,12 @@ void ApiTestFuzzer::CallTest() {
static v8::Handle<Value> ThrowInJS(const v8::Arguments& args) { static v8::Handle<Value> ThrowInJS(const v8::Arguments& args) {
CHECK(v8::Locker::IsLocked()); CHECK(v8::Locker::IsLocked(CcTest::default_isolate()));
ApiTestFuzzer::Fuzz(); ApiTestFuzzer::Fuzz();
v8::Unlocker unlocker; v8::Unlocker unlocker(CcTest::default_isolate());
const char* code = "throw 7;"; const char* code = "throw 7;";
{ {
v8::Locker nested_locker; v8::Locker nested_locker(CcTest::default_isolate());
v8::HandleScope scope; v8::HandleScope scope;
v8::Handle<Value> exception; v8::Handle<Value> exception;
{ v8::TryCatch try_catch; { v8::TryCatch try_catch;
...@@ -11156,12 +11156,12 @@ static v8::Handle<Value> ThrowInJS(const v8::Arguments& args) { ...@@ -11156,12 +11156,12 @@ static v8::Handle<Value> ThrowInJS(const v8::Arguments& args) {
static v8::Handle<Value> ThrowInJSNoCatch(const v8::Arguments& args) { static v8::Handle<Value> ThrowInJSNoCatch(const v8::Arguments& args) {
CHECK(v8::Locker::IsLocked()); CHECK(v8::Locker::IsLocked(CcTest::default_isolate()));
ApiTestFuzzer::Fuzz(); ApiTestFuzzer::Fuzz();
v8::Unlocker unlocker; v8::Unlocker unlocker(CcTest::default_isolate());
const char* code = "throw 7;"; const char* code = "throw 7;";
{ {
v8::Locker nested_locker; v8::Locker nested_locker(CcTest::default_isolate());
v8::HandleScope scope; v8::HandleScope scope;
v8::Handle<Value> value = CompileRun(code); v8::Handle<Value> value = CompileRun(code);
CHECK(value.IsEmpty()); CHECK(value.IsEmpty());
...@@ -11173,8 +11173,8 @@ static v8::Handle<Value> ThrowInJSNoCatch(const v8::Arguments& args) { ...@@ -11173,8 +11173,8 @@ static v8::Handle<Value> ThrowInJSNoCatch(const v8::Arguments& args) {
// These are locking tests that don't need to be run again // These are locking tests that don't need to be run again
// as part of the locking aggregation tests. // as part of the locking aggregation tests.
TEST(NestedLockers) { TEST(NestedLockers) {
v8::Locker locker; v8::Locker locker(CcTest::default_isolate());
CHECK(v8::Locker::IsLocked()); CHECK(v8::Locker::IsLocked(CcTest::default_isolate()));
v8::HandleScope scope; v8::HandleScope scope;
LocalContext env; LocalContext env;
Local<v8::FunctionTemplate> fun_templ = v8::FunctionTemplate::New(ThrowInJS); Local<v8::FunctionTemplate> fun_templ = v8::FunctionTemplate::New(ThrowInJS);
...@@ -11195,7 +11195,7 @@ TEST(NestedLockers) { ...@@ -11195,7 +11195,7 @@ TEST(NestedLockers) {
// These are locking tests that don't need to be run again // These are locking tests that don't need to be run again
// as part of the locking aggregation tests. // as part of the locking aggregation tests.
TEST(NestedLockersNoTryCatch) { TEST(NestedLockersNoTryCatch) {
v8::Locker locker; v8::Locker locker(CcTest::default_isolate());
v8::HandleScope scope; v8::HandleScope scope;
LocalContext env; LocalContext env;
Local<v8::FunctionTemplate> fun_templ = Local<v8::FunctionTemplate> fun_templ =
...@@ -11215,24 +11215,24 @@ TEST(NestedLockersNoTryCatch) { ...@@ -11215,24 +11215,24 @@ TEST(NestedLockersNoTryCatch) {
THREADED_TEST(RecursiveLocking) { THREADED_TEST(RecursiveLocking) {
v8::Locker locker; v8::Locker locker(CcTest::default_isolate());
{ {
v8::Locker locker2; v8::Locker locker2(CcTest::default_isolate());
CHECK(v8::Locker::IsLocked()); CHECK(v8::Locker::IsLocked(CcTest::default_isolate()));
} }
} }
static v8::Handle<Value> UnlockForAMoment(const v8::Arguments& args) { static v8::Handle<Value> UnlockForAMoment(const v8::Arguments& args) {
ApiTestFuzzer::Fuzz(); ApiTestFuzzer::Fuzz();
v8::Unlocker unlocker; v8::Unlocker unlocker(CcTest::default_isolate());
return v8::Undefined(); return v8::Undefined();
} }
THREADED_TEST(LockUnlockLock) { THREADED_TEST(LockUnlockLock) {
{ {
v8::Locker locker; v8::Locker locker(CcTest::default_isolate());
v8::HandleScope scope; v8::HandleScope scope;
LocalContext env; LocalContext env;
Local<v8::FunctionTemplate> fun_templ = Local<v8::FunctionTemplate> fun_templ =
...@@ -11246,7 +11246,7 @@ THREADED_TEST(LockUnlockLock) { ...@@ -11246,7 +11246,7 @@ THREADED_TEST(LockUnlockLock) {
CHECK_EQ(42, script->Run()->Int32Value()); CHECK_EQ(42, script->Run()->Int32Value());
} }
{ {
v8::Locker locker; v8::Locker locker(CcTest::default_isolate());
v8::HandleScope scope; v8::HandleScope scope;
LocalContext env; LocalContext env;
Local<v8::FunctionTemplate> fun_templ = Local<v8::FunctionTemplate> fun_templ =
...@@ -12497,7 +12497,7 @@ class RegExpInterruptTest { ...@@ -12497,7 +12497,7 @@ class RegExpInterruptTest {
LongRunningRegExp(); LongRunningRegExp();
{ {
v8::Unlocker unlock; v8::Unlocker unlock(CcTest::default_isolate());
gc_thread.Join(); gc_thread.Join();
} }
v8::Locker::StopPreemption(); v8::Locker::StopPreemption();
...@@ -12524,7 +12524,7 @@ class RegExpInterruptTest { ...@@ -12524,7 +12524,7 @@ class RegExpInterruptTest {
block_->Wait(); block_->Wait();
while (gc_during_regexp_ < kRequiredGCs) { while (gc_during_regexp_ < kRequiredGCs) {
{ {
v8::Locker lock; v8::Locker lock(CcTest::default_isolate());
// TODO(lrn): Perhaps create some garbage before collecting. // TODO(lrn): Perhaps create some garbage before collecting.
HEAP->CollectAllGarbage(i::Heap::kNoGCFlags); HEAP->CollectAllGarbage(i::Heap::kNoGCFlags);
gc_count_++; gc_count_++;
...@@ -12584,7 +12584,7 @@ class RegExpInterruptTest { ...@@ -12584,7 +12584,7 @@ class RegExpInterruptTest {
// Test that a regular expression execution can be interrupted and // Test that a regular expression execution can be interrupted and
// survive a garbage collection. // survive a garbage collection.
TEST(RegExpInterruption) { TEST(RegExpInterruption) {
v8::Locker lock; v8::Locker lock(CcTest::default_isolate());
v8::V8::Initialize(); v8::V8::Initialize();
v8::HandleScope scope; v8::HandleScope scope;
Local<Context> local_env; Local<Context> local_env;
...@@ -12620,7 +12620,7 @@ class ApplyInterruptTest { ...@@ -12620,7 +12620,7 @@ class ApplyInterruptTest {
LongRunningApply(); LongRunningApply();
{ {
v8::Unlocker unlock; v8::Unlocker unlock(CcTest::default_isolate());
gc_thread.Join(); gc_thread.Join();
} }
v8::Locker::StopPreemption(); v8::Locker::StopPreemption();
...@@ -12647,7 +12647,7 @@ class ApplyInterruptTest { ...@@ -12647,7 +12647,7 @@ class ApplyInterruptTest {
block_->Wait(); block_->Wait();
while (gc_during_apply_ < kRequiredGCs) { while (gc_during_apply_ < kRequiredGCs) {
{ {
v8::Locker lock; v8::Locker lock(CcTest::default_isolate());
HEAP->CollectAllGarbage(i::Heap::kNoGCFlags); HEAP->CollectAllGarbage(i::Heap::kNoGCFlags);
gc_count_++; gc_count_++;
} }
...@@ -12693,7 +12693,7 @@ class ApplyInterruptTest { ...@@ -12693,7 +12693,7 @@ class ApplyInterruptTest {
// Test that nothing bad happens if we get a preemption just when we were // Test that nothing bad happens if we get a preemption just when we were
// about to do an apply(). // about to do an apply().
TEST(ApplyInterruption) { TEST(ApplyInterruption) {
v8::Locker lock; v8::Locker lock(CcTest::default_isolate());
v8::V8::Initialize(); v8::V8::Initialize();
v8::HandleScope scope; v8::HandleScope scope;
Local<Context> local_env; Local<Context> local_env;
...@@ -12931,7 +12931,7 @@ class RegExpStringModificationTest { ...@@ -12931,7 +12931,7 @@ class RegExpStringModificationTest {
v8::Locker::StartPreemption(1); v8::Locker::StartPreemption(1);
LongRunningRegExp(); LongRunningRegExp();
{ {
v8::Unlocker unlock; v8::Unlocker unlock(CcTest::default_isolate());
morph_thread.Join(); morph_thread.Join();
} }
v8::Locker::StopPreemption(); v8::Locker::StopPreemption();
...@@ -12960,7 +12960,7 @@ class RegExpStringModificationTest { ...@@ -12960,7 +12960,7 @@ class RegExpStringModificationTest {
while (morphs_during_regexp_ < kRequiredModifications && while (morphs_during_regexp_ < kRequiredModifications &&
morphs_ < kMaxModifications) { morphs_ < kMaxModifications) {
{ {
v8::Locker lock; v8::Locker lock(CcTest::default_isolate());
// Swap string between ascii and two-byte representation. // Swap string between ascii and two-byte representation.
i::String* string = *input_; i::String* string = *input_;
MorphAString(string, &ascii_resource_, &uc16_resource_); MorphAString(string, &ascii_resource_, &uc16_resource_);
...@@ -13008,7 +13008,7 @@ class RegExpStringModificationTest { ...@@ -13008,7 +13008,7 @@ class RegExpStringModificationTest {
// Test that a regular expression execution can be interrupted and // Test that a regular expression execution can be interrupted and
// the string changed without failing. // the string changed without failing.
TEST(RegExpStringModification) { TEST(RegExpStringModification) {
v8::Locker lock; v8::Locker lock(CcTest::default_isolate());
v8::V8::Initialize(); v8::V8::Initialize();
v8::HandleScope scope; v8::HandleScope scope;
Local<Context> local_env; Local<Context> local_env;
...@@ -15141,7 +15141,7 @@ TEST(SetResourceConstraints) { ...@@ -15141,7 +15141,7 @@ TEST(SetResourceConstraints) {
TEST(SetResourceConstraintsInThread) { TEST(SetResourceConstraintsInThread) {
uint32_t* set_limit; uint32_t* set_limit;
{ {
v8::Locker locker; v8::Locker locker(CcTest::default_isolate());
static const int K = 1024; static const int K = 1024;
set_limit = ComputeStackLimit(128 * K); set_limit = ComputeStackLimit(128 * K);
...@@ -15162,7 +15162,7 @@ TEST(SetResourceConstraintsInThread) { ...@@ -15162,7 +15162,7 @@ TEST(SetResourceConstraintsInThread) {
CHECK(stack_limit == set_limit); CHECK(stack_limit == set_limit);
} }
{ {
v8::Locker locker; v8::Locker locker(CcTest::default_isolate());
CHECK(stack_limit == set_limit); CHECK(stack_limit == set_limit);
} }
} }
......
...@@ -536,7 +536,7 @@ class LockUnlockLockThread : public JoinableThread { ...@@ -536,7 +536,7 @@ class LockUnlockLockThread : public JoinableThread {
virtual void Run() { virtual void Run() {
v8::Locker lock1(isolate_); v8::Locker lock1(isolate_);
CHECK(v8::Locker::IsLocked(isolate_)); CHECK(v8::Locker::IsLocked(isolate_));
CHECK(!v8::Locker::IsLocked()); CHECK(!v8::Locker::IsLocked(CcTest::default_isolate()));
{ {
v8::Isolate::Scope isolate_scope(isolate_); v8::Isolate::Scope isolate_scope(isolate_);
v8::HandleScope handle_scope; v8::HandleScope handle_scope;
...@@ -546,13 +546,13 @@ class LockUnlockLockThread : public JoinableThread { ...@@ -546,13 +546,13 @@ class LockUnlockLockThread : public JoinableThread {
{ {
v8::Unlocker unlock1(isolate_); v8::Unlocker unlock1(isolate_);
CHECK(!v8::Locker::IsLocked(isolate_)); CHECK(!v8::Locker::IsLocked(isolate_));
CHECK(!v8::Locker::IsLocked()); CHECK(!v8::Locker::IsLocked(CcTest::default_isolate()));
{ {
v8::Locker lock2(isolate_); v8::Locker lock2(isolate_);
v8::Isolate::Scope isolate_scope(isolate_); v8::Isolate::Scope isolate_scope(isolate_);
v8::HandleScope handle_scope; v8::HandleScope handle_scope;
CHECK(v8::Locker::IsLocked(isolate_)); CHECK(v8::Locker::IsLocked(isolate_));
CHECK(!v8::Locker::IsLocked()); CHECK(!v8::Locker::IsLocked(CcTest::default_isolate()));
v8::Context::Scope context_scope(context_); v8::Context::Scope context_scope(context_);
CalcFibAndCheck(); CalcFibAndCheck();
} }
...@@ -594,16 +594,16 @@ class LockUnlockLockDefaultIsolateThread : public JoinableThread { ...@@ -594,16 +594,16 @@ class LockUnlockLockDefaultIsolateThread : public JoinableThread {
} }
virtual void Run() { virtual void Run() {
v8::Locker lock1; v8::Locker lock1(CcTest::default_isolate());
{ {
v8::HandleScope handle_scope; v8::HandleScope handle_scope;
v8::Context::Scope context_scope(context_); v8::Context::Scope context_scope(context_);
CalcFibAndCheck(); CalcFibAndCheck();
} }
{ {
v8::Unlocker unlock1; v8::Unlocker unlock1(CcTest::default_isolate());
{ {
v8::Locker lock2; v8::Locker lock2(CcTest::default_isolate());
v8::HandleScope handle_scope; v8::HandleScope handle_scope;
v8::Context::Scope context_scope(context_); v8::Context::Scope context_scope(context_);
CalcFibAndCheck(); CalcFibAndCheck();
...@@ -624,7 +624,7 @@ TEST(LockUnlockLockDefaultIsolateMultithreaded) { ...@@ -624,7 +624,7 @@ TEST(LockUnlockLockDefaultIsolateMultithreaded) {
#endif #endif
Persistent<v8::Context> context; Persistent<v8::Context> context;
{ {
v8::Locker locker_; v8::Locker locker_(CcTest::default_isolate());
v8::HandleScope handle_scope; v8::HandleScope handle_scope;
context = v8::Context::New(); context = v8::Context::New();
} }
......
...@@ -202,7 +202,7 @@ class LoopingThread : public v8::internal::Thread { ...@@ -202,7 +202,7 @@ class LoopingThread : public v8::internal::Thread {
public: public:
LoopingThread() : Thread("LoopingThread") { } LoopingThread() : Thread("LoopingThread") { }
void Run() { void Run() {
v8::Locker locker; v8::Locker locker(CcTest::default_isolate());
v8::HandleScope scope; v8::HandleScope scope;
v8_thread_id_ = v8::V8::GetCurrentThreadId(); v8_thread_id_ = v8::V8::GetCurrentThreadId();
v8::Handle<v8::ObjectTemplate> global = v8::Handle<v8::ObjectTemplate> global =
...@@ -228,7 +228,7 @@ class LoopingThread : public v8::internal::Thread { ...@@ -228,7 +228,7 @@ class LoopingThread : public v8::internal::Thread {
// from another thread when using Lockers and preemption. // from another thread when using Lockers and preemption.
TEST(TerminateMultipleV8ThreadsDefaultIsolate) { TEST(TerminateMultipleV8ThreadsDefaultIsolate) {
{ {
v8::Locker locker; v8::Locker locker(CcTest::default_isolate());
v8::V8::Initialize(); v8::V8::Initialize();
v8::Locker::StartPreemption(1); v8::Locker::StartPreemption(1);
semaphore = v8::internal::OS::CreateSemaphore(0); semaphore = v8::internal::OS::CreateSemaphore(0);
...@@ -246,7 +246,7 @@ TEST(TerminateMultipleV8ThreadsDefaultIsolate) { ...@@ -246,7 +246,7 @@ TEST(TerminateMultipleV8ThreadsDefaultIsolate) {
semaphore->Wait(); semaphore->Wait();
} }
{ {
v8::Locker locker; v8::Locker locker(CcTest::default_isolate());
for (int i = 0; i < kThreads; i++) { for (int i = 0; i < kThreads; i++) {
v8::V8::TerminateExecution(threads[i]->GetV8ThreadId()); v8::V8::TerminateExecution(threads[i]->GetV8ThreadId());
} }
...@@ -256,7 +256,7 @@ TEST(TerminateMultipleV8ThreadsDefaultIsolate) { ...@@ -256,7 +256,7 @@ TEST(TerminateMultipleV8ThreadsDefaultIsolate) {
delete threads[i]; delete threads[i];
} }
{ {
v8::Locker locker; v8::Locker locker(CcTest::default_isolate());
v8::Locker::StopPreemption(); v8::Locker::StopPreemption();
} }
...@@ -372,4 +372,3 @@ TEST(TerminateAndReenterFromThreadItself) { ...@@ -372,4 +372,3 @@ TEST(TerminateAndReenterFromThreadItself) {
"f()"))->Run()->IsTrue()); "f()"))->Run()->IsTrue());
context.Dispose(); context.Dispose();
} }
...@@ -34,7 +34,7 @@ ...@@ -34,7 +34,7 @@
TEST(Preemption) { TEST(Preemption) {
v8::Locker locker; v8::Locker locker(CcTest::default_isolate());
v8::V8::Initialize(); v8::V8::Initialize();
v8::HandleScope scope; v8::HandleScope scope;
v8::Context::Scope context_scope(v8::Context::New()); v8::Context::Scope context_scope(v8::Context::New());
...@@ -67,7 +67,7 @@ class ThreadA : public v8::internal::Thread { ...@@ -67,7 +67,7 @@ class ThreadA : public v8::internal::Thread {
public: public:
ThreadA() : Thread("ThreadA") { } ThreadA() : Thread("ThreadA") { }
void Run() { void Run() {
v8::Locker locker; v8::Locker locker(CcTest::default_isolate());
v8::HandleScope scope; v8::HandleScope scope;
v8::Context::Scope context_scope(v8::Context::New()); v8::Context::Scope context_scope(v8::Context::New());
...@@ -86,7 +86,7 @@ class ThreadA : public v8::internal::Thread { ...@@ -86,7 +86,7 @@ class ThreadA : public v8::internal::Thread {
turn = CLEAN_CACHE; turn = CLEAN_CACHE;
do { do {
{ {
v8::Unlocker unlocker; v8::Unlocker unlocker(CcTest::default_isolate());
Thread::YieldCPU(); Thread::YieldCPU();
} }
} while (turn != SECOND_TIME_FILL_CACHE); } while (turn != SECOND_TIME_FILL_CACHE);
...@@ -105,7 +105,7 @@ class ThreadB : public v8::internal::Thread { ...@@ -105,7 +105,7 @@ class ThreadB : public v8::internal::Thread {
void Run() { void Run() {
do { do {
{ {
v8::Locker locker; v8::Locker locker(CcTest::default_isolate());
if (turn == CLEAN_CACHE) { if (turn == CLEAN_CACHE) {
v8::HandleScope scope; v8::HandleScope scope;
v8::Context::Scope context_scope(v8::Context::New()); v8::Context::Scope context_scope(v8::Context::New());
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment