Skip to content

Commit 78796c8

Browse files
authored
Move module loading out of js::core::Context, so it can be customised (#6199)
1 parent a358bfa commit 78796c8

14 files changed

+377
-214
lines changed

.daily_canary

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,4 @@
33
( V ) / . \ | +---=---'
44
/--x-m- /--n-n---xXx--/--yY------>>>----<<<>>]]{{}}---||-/\---..
55
2024__
6-
!"!
6+
!

include/ccf/js/core/context.h

+13-7
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
#include "ccf/js/core/runtime.h"
66
#include "ccf/js/core/wrapped_value.h"
77
#include "ccf/js/extensions/extension_interface.h"
8+
#include "ccf/js/modules/module_loader_interface.h"
89
#include "ccf/js/tx_access.h"
910
#include "ccf/pal/locking.h"
1011

@@ -46,14 +47,16 @@ namespace ccf::js::core
4647
JSContext* ctx;
4748
Runtime rt;
4849

50+
js::extensions::Extensions extensions;
51+
js::modules::ModuleLoaderPtr module_loader;
52+
4953
// The interpreter can cache loaded modules so they do not need to be loaded
5054
// from the KV for every execution, which is particularly useful when
5155
// re-using interpreters. A module can only be loaded once per interpreter,
5256
// and the entire interpreter should be thrown away if _any_ of its modules
5357
// needs to be refreshed.
54-
std::map<std::string, JSWrappedValue> loaded_modules_cache;
55-
56-
js::extensions::Extensions extensions;
58+
std::map<std::string, js::core::JSWrappedValue, std::less<>>
59+
loaded_modules_cache;
5760

5861
public:
5962
ccf::pal::Mutex lock;
@@ -82,10 +85,13 @@ namespace ccf::js::core
8285
return ctx;
8386
}
8487

85-
std::optional<JSWrappedValue> get_module_from_cache(
86-
const std::string& module_name);
87-
void load_module_to_cache(
88-
const std::string& module_name, const JSWrappedValue& module);
88+
void set_module_loader(const modules::ModuleLoaderPtr& ml)
89+
{
90+
module_loader = ml;
91+
}
92+
93+
virtual std::optional<JSWrappedValue> get_module(
94+
std::string_view module_name);
8995

9096
// Construct RAII wrapper around raw QuickJS value
9197
JSWrappedValue wrap(JSValue&& val) const;

include/ccf/js/modules.h

-157
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the Apache 2.0 License.
3+
#pragma once
4+
5+
#include "ccf/js/modules/module_loader_interface.h"
6+
7+
namespace ccf::js::modules
8+
{
9+
class ChainedModuleLoader : public ModuleLoaderInterface
10+
{
11+
protected:
12+
ModuleLoaders sub_loaders;
13+
14+
public:
15+
ChainedModuleLoader(ModuleLoaders&& ml) : sub_loaders(std::move(ml)) {}
16+
17+
virtual std::optional<js::core::JSWrappedValue> get_module(
18+
std::string_view module_name, js::core::Context& ctx) override
19+
{
20+
for (auto& sub_loader : sub_loaders)
21+
{
22+
auto module_val = sub_loader->get_module(module_name, ctx);
23+
if (module_val.has_value())
24+
{
25+
return module_val;
26+
}
27+
}
28+
29+
return std::nullopt;
30+
}
31+
};
32+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the Apache 2.0 License.
3+
#pragma once
4+
5+
#include "ccf/js/modules/module_loader_interface.h"
6+
#include "ccf/service/tables/modules.h"
7+
#include "ccf/tx.h"
8+
#include "ccf/version.h"
9+
10+
#include <string>
11+
12+
namespace ccf::js::modules
13+
{
14+
class KvBytecodeModuleLoader : public ModuleLoaderInterface
15+
{
16+
protected:
17+
ccf::ModulesQuickJsBytecode::ReadOnlyHandle* modules_bytecode_handle;
18+
19+
bool version_ok;
20+
21+
public:
22+
KvBytecodeModuleLoader(
23+
ccf::ModulesQuickJsBytecode::ReadOnlyHandle* mbh,
24+
ccf::ModulesQuickJsVersion::ReadOnlyHandle* modules_version_handle) :
25+
modules_bytecode_handle(mbh)
26+
{
27+
const auto version_in_kv = modules_version_handle->get();
28+
const auto version_in_binary = std::string(ccf::quickjs_version);
29+
if (version_in_kv != version_in_binary)
30+
{
31+
CCF_APP_INFO(
32+
"Ignoring bytecode table, which was written for QuickJS {} (this "
33+
"node is running QuickJS {})",
34+
version_in_kv,
35+
version_in_binary);
36+
version_ok = false;
37+
}
38+
else
39+
{
40+
version_ok = true;
41+
}
42+
}
43+
44+
virtual std::optional<js::core::JSWrappedValue> get_module(
45+
std::string_view module_name, js::core::Context& ctx) override
46+
{
47+
if (!version_ok)
48+
{
49+
return std::nullopt;
50+
}
51+
52+
std::string module_name_kv(module_name);
53+
if (module_name_kv[0] != '/')
54+
{
55+
module_name_kv.insert(0, "/");
56+
}
57+
58+
CCF_APP_TRACE("Looking for module '{}' bytecode in KV", module_name_kv);
59+
60+
auto module_bytecode = modules_bytecode_handle->get(module_name_kv);
61+
if (!module_bytecode.has_value())
62+
{
63+
CCF_APP_TRACE("Module '{}' not found", module_name_kv);
64+
return std::nullopt;
65+
}
66+
67+
auto module_val = ctx.read_object(
68+
module_bytecode->data(), module_bytecode->size(), JS_READ_OBJ_BYTECODE);
69+
70+
const bool failed_deser = module_val.is_exception();
71+
const bool failed_resolve =
72+
!failed_deser && (JS_ResolveModule(ctx, module_val.val) < 0);
73+
74+
if (failed_deser || failed_resolve)
75+
{
76+
auto [reason, trace] = ctx.error_message();
77+
78+
auto& rt = ctx.runtime();
79+
if (rt.log_exception_details)
80+
{
81+
CCF_APP_FAIL("{}: {}", reason, trace.value_or("<no trace>"));
82+
}
83+
84+
if (failed_deser)
85+
{
86+
throw std::runtime_error(fmt::format(
87+
"Failed to deserialize bytecode for module '{}': {}",
88+
module_name,
89+
reason));
90+
}
91+
else
92+
{
93+
throw std::runtime_error(fmt::format(
94+
"Failed to resolve dependencies for module '{}': {}",
95+
module_name,
96+
reason));
97+
}
98+
}
99+
100+
CCF_APP_TRACE(
101+
"Module '{}' bytecode found in KV (table: {})",
102+
module_name_kv,
103+
modules_bytecode_handle->get_name_of_map());
104+
return module_val;
105+
}
106+
};
107+
}

0 commit comments

Comments
 (0)