I think I need to change the documentation format
This commit is contained in:
+309
-82
@@ -1,9 +1,11 @@
|
||||
#include "compiler.h"
|
||||
#include "compiler-parser.h"
|
||||
#include "hash.h"
|
||||
#include <algorithm>
|
||||
#include <cstdlib>
|
||||
#include <filesystem>
|
||||
#include <sys/file.h>
|
||||
#include <unistd.h>
|
||||
|
||||
namespace {
|
||||
|
||||
@@ -17,11 +19,14 @@ struct SharedUnitFilesystemState
|
||||
{
|
||||
bool source_exists = false;
|
||||
bool metadata_exists = false;
|
||||
bool compile_output_exists = false;
|
||||
bool metadata_parsed = false;
|
||||
bool abi_compatible = false;
|
||||
bool input_signature_matches = false;
|
||||
time_t source_time = 0;
|
||||
time_t compiled_time = 0;
|
||||
time_t metadata_time = 0;
|
||||
time_t compile_output_time = 0;
|
||||
time_t setup_template_time = 0;
|
||||
time_t compiler_header_time = 0;
|
||||
time_t compiler_source_time = 0;
|
||||
@@ -30,8 +35,51 @@ struct SharedUnitFilesystemState
|
||||
time_t required_time = 0;
|
||||
u64 metadata_abi_version = 0;
|
||||
u64 runtime_abi_version = UCE_UNIT_ABI_VERSION;
|
||||
String metadata_content;
|
||||
String compile_output_content;
|
||||
String metadata_build_token;
|
||||
String metadata_input_signature;
|
||||
String current_input_signature;
|
||||
};
|
||||
|
||||
struct SharedUnitCompileCheck
|
||||
{
|
||||
bool source_missing = false;
|
||||
bool needs_compile = false;
|
||||
};
|
||||
|
||||
bool compiler_config_truthy(String raw, bool default_value)
|
||||
{
|
||||
raw = to_lower(trim(raw));
|
||||
if(raw == "")
|
||||
return(default_value);
|
||||
if(raw == "1" || raw == "true" || raw == "yes" || raw == "on")
|
||||
return(true);
|
||||
if(raw == "0" || raw == "false" || raw == "no" || raw == "off")
|
||||
return(false);
|
||||
return(default_value);
|
||||
}
|
||||
|
||||
bool compiler_jit_compile_on_request_enabled(Request* context)
|
||||
{
|
||||
if(!context || !context->server)
|
||||
return(true);
|
||||
return(compiler_config_truthy(context->server->config["JIT_COMPILE_ON_REQUEST"], true));
|
||||
}
|
||||
|
||||
u64 compiler_failure_retry_seconds(Request* context)
|
||||
{
|
||||
if(!context || !context->server)
|
||||
return(10);
|
||||
auto raw = trim(context->server->config["COMPILE_FAILURE_RETRY_SECONDS"]);
|
||||
if(raw == "")
|
||||
return(10);
|
||||
auto value = int_val(raw);
|
||||
if(value < 0)
|
||||
return(0);
|
||||
return((u64)value);
|
||||
}
|
||||
|
||||
bool compiler_is_u64_string(String value)
|
||||
{
|
||||
value = trim(value);
|
||||
@@ -45,11 +93,12 @@ bool compiler_is_u64_string(String value)
|
||||
return(true);
|
||||
}
|
||||
|
||||
bool compiler_parse_unit_metadata_abi(String content, u64& abi_out)
|
||||
StringMap compiler_parse_unit_metadata(String content)
|
||||
{
|
||||
StringMap metadata;
|
||||
content = trim(content);
|
||||
if(content == "")
|
||||
return(false);
|
||||
return(metadata);
|
||||
|
||||
auto lines = split(content, "\n");
|
||||
for(auto& raw_line : lines)
|
||||
@@ -62,37 +111,97 @@ bool compiler_parse_unit_metadata_abi(String content, u64& abi_out)
|
||||
continue;
|
||||
auto key = trim(line.substr(0, split_pos));
|
||||
auto value = trim(line.substr(split_pos + 1));
|
||||
if(key != "unit_abi_version" || !compiler_is_u64_string(value))
|
||||
continue;
|
||||
abi_out = (u64)atoll(value.c_str());
|
||||
return(true);
|
||||
if(key != "")
|
||||
metadata[key] = value;
|
||||
}
|
||||
return(false);
|
||||
return(metadata);
|
||||
}
|
||||
|
||||
String compiler_unit_metadata_text()
|
||||
String compiler_unit_input_signature(Request* context, SharedUnit* su)
|
||||
{
|
||||
if(!context || !su || !file_exists(su->file_name))
|
||||
return("");
|
||||
|
||||
String setup_template = context->server->config["COMPILER_SYS_PATH"] + "/" + context->server->config["SETUP_TEMPLATE"];
|
||||
return(
|
||||
gen_sha1(file_get_contents(su->file_name)) + ":" +
|
||||
gen_sha1(file_get_contents(setup_template)) + ":" +
|
||||
std::to_string(UCE_UNIT_ABI_VERSION)
|
||||
);
|
||||
}
|
||||
|
||||
String compiler_unit_build_token()
|
||||
{
|
||||
return(
|
||||
std::to_string(getpid()) + ":" +
|
||||
std::to_string((u64)(time_precise() * 1000000.0))
|
||||
);
|
||||
}
|
||||
|
||||
String compiler_unit_metadata_text(Request* context, SharedUnit* su)
|
||||
{
|
||||
return(
|
||||
"format=uce-unit-metadata-v1\n"
|
||||
"unit_abi_version=" + std::to_string(UCE_UNIT_ABI_VERSION) + "\n"
|
||||
"input_signature=" + compiler_unit_input_signature(context, su) + "\n"
|
||||
"build_token=" + compiler_unit_build_token() + "\n"
|
||||
);
|
||||
}
|
||||
|
||||
bool shared_unit_filesystem_requires_recompile(const SharedUnitFilesystemState& state)
|
||||
SharedUnitCompileCheck shared_unit_compile_check(const SharedUnitFilesystemState& state)
|
||||
{
|
||||
if(!state.source_exists)
|
||||
return(true);
|
||||
if(state.compiled_time == 0)
|
||||
return(true);
|
||||
if(state.compiled_time < state.required_time)
|
||||
return(true);
|
||||
if(!state.metadata_exists)
|
||||
return(true);
|
||||
if(!state.metadata_parsed)
|
||||
return(true);
|
||||
if(!state.abi_compatible)
|
||||
return(true);
|
||||
return(false);
|
||||
SharedUnitCompileCheck result;
|
||||
result.source_missing = !state.source_exists;
|
||||
result.needs_compile =
|
||||
!state.source_exists ||
|
||||
state.compiled_time == 0 ||
|
||||
state.compiled_time < state.required_time ||
|
||||
!state.metadata_exists ||
|
||||
!state.metadata_parsed ||
|
||||
!state.abi_compatible ||
|
||||
!state.input_signature_matches;
|
||||
return(result);
|
||||
}
|
||||
|
||||
bool compiler_failure_retry_deferred(Request* context, SharedUnit* su, const SharedUnitFilesystemState& state)
|
||||
{
|
||||
if(!context || !su)
|
||||
return(false);
|
||||
auto retry_seconds = compiler_failure_retry_seconds(context);
|
||||
if(retry_seconds == 0)
|
||||
return(false);
|
||||
if(!state.compile_output_exists || state.compile_output_time == 0)
|
||||
return(false);
|
||||
if(state.source_time == 0)
|
||||
return(false);
|
||||
if(state.source_time > state.compile_output_time)
|
||||
return(false);
|
||||
return(time() < (u64)state.compile_output_time + retry_seconds);
|
||||
}
|
||||
|
||||
String compiler_failure_output_for_state(SharedUnit* su, const SharedUnitFilesystemState& state)
|
||||
{
|
||||
if(!state.compile_output_exists)
|
||||
return("");
|
||||
auto content = trim(state.compile_output_content);
|
||||
if(content != "")
|
||||
return(content);
|
||||
if(su)
|
||||
return(trim(su->compile_error_status));
|
||||
return("");
|
||||
}
|
||||
|
||||
void compiler_restore_persisted_failure(SharedUnit* su, const SharedUnitFilesystemState& state, String status = "compile_error")
|
||||
{
|
||||
if(!su)
|
||||
return;
|
||||
auto message = compiler_failure_output_for_state(su, state);
|
||||
if(message == "")
|
||||
message = "recent compilation failed";
|
||||
su->compiler_messages = message;
|
||||
su->compile_status = status;
|
||||
su->compile_error_status = message;
|
||||
su->last_error = (state.compile_output_time != 0 ? state.compile_output_time : time());
|
||||
}
|
||||
|
||||
time_t compiler_runtime_abi_time(Request* context)
|
||||
@@ -196,6 +305,16 @@ auto compiler_with_registry_lock(Request* context, TCallback callback) -> declty
|
||||
return(result);
|
||||
}
|
||||
|
||||
bool compiler_has_known_unit_cached(Request* context, String file_name)
|
||||
{
|
||||
if(!context)
|
||||
return(false);
|
||||
return(compiler_with_registry_lock(context, [&]() {
|
||||
auto files = compiler_read_known_units_unlocked(context);
|
||||
return(std::find(files.begin(), files.end(), file_name) != files.end());
|
||||
}));
|
||||
}
|
||||
|
||||
SharedUnitFilesystemState inspect_shared_unit_filesystem(Request* context, SharedUnit* su)
|
||||
{
|
||||
SharedUnitFilesystemState state;
|
||||
@@ -211,27 +330,60 @@ SharedUnitFilesystemState inspect_shared_unit_filesystem(Request* context, Share
|
||||
state.runtime_binary_time = file_mtime(context->server->config["COMPILER_SYS_PATH"] + "/bin/uce_fastcgi.linux.bin");
|
||||
state.compiler_abi_time = compiler_runtime_abi_time(context);
|
||||
state.metadata_time = file_mtime(su->meta_file_name);
|
||||
state.compile_output_time = file_mtime(su->compile_output_file_name);
|
||||
state.current_input_signature = compiler_unit_input_signature(context, su);
|
||||
state.metadata_exists = (state.metadata_time != 0);
|
||||
state.compile_output_exists = (state.compile_output_time != 0);
|
||||
if(state.metadata_exists)
|
||||
state.metadata_parsed = compiler_parse_unit_metadata_abi(
|
||||
file_get_contents(su->meta_file_name),
|
||||
state.metadata_abi_version
|
||||
);
|
||||
{
|
||||
state.metadata_content = file_get_contents(su->meta_file_name);
|
||||
auto metadata = compiler_parse_unit_metadata(state.metadata_content);
|
||||
auto abi_it = metadata.find("unit_abi_version");
|
||||
if(abi_it != metadata.end() && compiler_is_u64_string(abi_it->second))
|
||||
{
|
||||
state.metadata_abi_version = (u64)atoll(abi_it->second.c_str());
|
||||
state.metadata_parsed = true;
|
||||
}
|
||||
auto input_it = metadata.find("input_signature");
|
||||
if(input_it != metadata.end())
|
||||
state.metadata_input_signature = input_it->second;
|
||||
auto build_it = metadata.find("build_token");
|
||||
if(build_it != metadata.end())
|
||||
state.metadata_build_token = build_it->second;
|
||||
}
|
||||
if(state.compile_output_exists)
|
||||
state.compile_output_content = file_get_contents(su->compile_output_file_name);
|
||||
state.abi_compatible = (state.metadata_parsed && state.metadata_abi_version == state.runtime_abi_version);
|
||||
state.input_signature_matches = (
|
||||
state.metadata_parsed &&
|
||||
state.metadata_input_signature != "" &&
|
||||
state.current_input_signature != "" &&
|
||||
state.metadata_input_signature == state.current_input_signature
|
||||
);
|
||||
state.required_time = std::max({state.source_time, state.setup_template_time, state.compiler_abi_time});
|
||||
state.compiled_time = file_mtime(su->so_name);
|
||||
return(state);
|
||||
}
|
||||
|
||||
void compiler_record_observed_filesystem_state(SharedUnit* su, const SharedUnitFilesystemState& state)
|
||||
{
|
||||
if(!su)
|
||||
return;
|
||||
su->observed_compiled_time = state.compiled_time;
|
||||
su->observed_metadata_content = state.metadata_content;
|
||||
}
|
||||
|
||||
bool shared_unit_cache_is_stale(Request* context, SharedUnit* su)
|
||||
{
|
||||
if(!su)
|
||||
return(true);
|
||||
|
||||
auto state = inspect_shared_unit_filesystem(context, su);
|
||||
if(shared_unit_filesystem_requires_recompile(state))
|
||||
if(shared_unit_compile_check(state).needs_compile)
|
||||
return(true);
|
||||
if(su->last_compiled != 0 && state.compiled_time != su->last_compiled)
|
||||
if(state.compiled_time != su->observed_compiled_time)
|
||||
return(true);
|
||||
if(state.metadata_content != su->observed_metadata_content)
|
||||
return(true);
|
||||
return(false);
|
||||
}
|
||||
@@ -245,6 +397,45 @@ void release_shared_unit_cache_entry(Request* context, String file_name)
|
||||
context->server->units.erase(it);
|
||||
}
|
||||
|
||||
SharedUnit* compiler_cached_unit(Request* context, String file_name)
|
||||
{
|
||||
auto it = context->server->units.find(file_name);
|
||||
if(it == context->server->units.end())
|
||||
return(0);
|
||||
return(it->second);
|
||||
}
|
||||
|
||||
bool compiler_cache_mode_matches(SharedUnit* su, bool opt_so_optional)
|
||||
{
|
||||
return(su && su->opt_so_optional == opt_so_optional);
|
||||
}
|
||||
|
||||
bool compiler_cached_unit_is_reusable(Request* context, SharedUnit* su, bool opt_so_optional, bool force_recompile)
|
||||
{
|
||||
return(
|
||||
!force_recompile &&
|
||||
compiler_cache_mode_matches(su, opt_so_optional) &&
|
||||
!shared_unit_cache_is_stale(context, su)
|
||||
);
|
||||
}
|
||||
|
||||
SharedUnit* compiler_reusable_cached_unit(Request* context, String file_name, bool opt_so_optional, bool force_recompile)
|
||||
{
|
||||
auto su = compiler_cached_unit(context, file_name);
|
||||
if(compiler_cached_unit_is_reusable(context, su, opt_so_optional, force_recompile))
|
||||
return(su);
|
||||
return(0);
|
||||
}
|
||||
|
||||
void compiler_release_cached_unit_if_needed(Request* context, String file_name, bool opt_so_optional, bool force_recompile)
|
||||
{
|
||||
auto su = compiler_cached_unit(context, file_name);
|
||||
if(!su)
|
||||
return;
|
||||
if(force_recompile || !compiler_cache_mode_matches(su, opt_so_optional) || shared_unit_cache_is_stale(context, su))
|
||||
release_shared_unit_cache_entry(context, file_name);
|
||||
}
|
||||
|
||||
String compiler_current_unit_path(Request* context)
|
||||
{
|
||||
if(!context)
|
||||
@@ -354,11 +545,12 @@ String compiler_error_status(SharedUnit* su)
|
||||
|
||||
String compiler_status_from_filesystem(const SharedUnitFilesystemState& state, SharedUnit* su = 0)
|
||||
{
|
||||
auto compile_check = shared_unit_compile_check(state);
|
||||
if(!state.source_exists)
|
||||
return("missing_source");
|
||||
if(state.compiled_time == 0 || !state.metadata_exists)
|
||||
return("not_compiled");
|
||||
if(shared_unit_filesystem_requires_recompile(state))
|
||||
if(compile_check.needs_compile)
|
||||
return("stale");
|
||||
if(su && su->so_handle)
|
||||
return("loaded");
|
||||
@@ -453,13 +645,12 @@ void setup_unit_paths(Request* context, SharedUnit* su, String file_name)
|
||||
su->so_name = su->bin_path + "/" + su->bin_file_name;
|
||||
su->api_file_name = su->bin_path + "/" + su->src_file_name + ".exports.txt";
|
||||
su->meta_file_name = su->bin_path + "/" + su->src_file_name + ".meta.txt";
|
||||
su->compile_output_file_name = su->bin_path + "/" + su->src_file_name + ".compile.txt";
|
||||
//su->setup_file_name = su->bin_path + "/" + su->src_file_name + ".setup.h";
|
||||
}
|
||||
|
||||
void load_shared_unit(Request* context, SharedUnit* su, String file_name)
|
||||
void load_shared_unit(Request* context, SharedUnit* su)
|
||||
{
|
||||
//setup_unit_paths(context, su, file_name);
|
||||
|
||||
su->on_render = 0;
|
||||
su->on_component = 0;
|
||||
su->on_websocket = 0;
|
||||
@@ -474,6 +665,12 @@ void load_shared_unit(Request* context, SharedUnit* su, String file_name)
|
||||
su->compile_status = "not_compiled";
|
||||
return;
|
||||
}
|
||||
auto fs_state = inspect_shared_unit_filesystem(context, su);
|
||||
if(compiler_failure_retry_deferred(context, su, fs_state))
|
||||
{
|
||||
compiler_restore_persisted_failure(su, fs_state);
|
||||
return;
|
||||
}
|
||||
//printf("(i) unit file not found: %s\n", su->so_name.c_str());
|
||||
su->compiler_messages = "unit file not found";
|
||||
su->compile_status = "not_compiled";
|
||||
@@ -525,14 +722,14 @@ void load_shared_unit(Request* context, SharedUnit* su, String file_name)
|
||||
return(result);
|
||||
}*/
|
||||
|
||||
void compile_shared_unit(Request* context, SharedUnit* su, String file_name)
|
||||
void compile_shared_unit(Request* context, SharedUnit* su)
|
||||
{
|
||||
//setup_unit_paths(context, su, file_name);
|
||||
f64 comp_start = time_precise();
|
||||
|
||||
if(!file_exists(su->file_name))
|
||||
{
|
||||
su->compiler_messages = "source file not found (" + su->file_name + ")";
|
||||
file_put_contents(su->compile_output_file_name, su->compiler_messages + "\n");
|
||||
compiler_untrack_known_unit(context, su->file_name);
|
||||
compiler_record_compile_result(su, time_precise() - comp_start, false, "missing_source", su->compiler_messages);
|
||||
return;
|
||||
@@ -554,14 +751,22 @@ void compile_shared_unit(Request* context, SharedUnit* su, String file_name)
|
||||
|
||||
if(su->compiler_messages.length() > 0)
|
||||
{
|
||||
file_put_contents(su->compile_output_file_name, su->compiler_messages + "\n");
|
||||
compiler_record_compile_result(su, time_precise() - comp_start, false, "compile_error", su->compiler_messages);
|
||||
printf("%s \n", su->compiler_messages.c_str());
|
||||
}
|
||||
else
|
||||
{
|
||||
load_shared_unit(context, su, file_name);
|
||||
load_shared_unit(context, su);
|
||||
if(su->so_handle)
|
||||
file_put_contents(su->meta_file_name, compiler_unit_metadata_text());
|
||||
{
|
||||
file_put_contents(su->meta_file_name, compiler_unit_metadata_text(context, su));
|
||||
file_unlink(su->compile_output_file_name);
|
||||
}
|
||||
else if(trim(su->compiler_messages) != "")
|
||||
{
|
||||
file_put_contents(su->compile_output_file_name, su->compiler_messages + "\n");
|
||||
}
|
||||
compiler_record_compile_result(
|
||||
su,
|
||||
time_precise() - comp_start,
|
||||
@@ -579,16 +784,11 @@ SharedUnit* compiler_get_shared_unit_internal(Request* context, String file_name
|
||||
{
|
||||
file_name = compiler_normalize_unit_path(context, file_name);
|
||||
|
||||
auto existing = context->server->units.find(file_name);
|
||||
bool cache_mode_matches = (
|
||||
existing != context->server->units.end() &&
|
||||
existing->second->opt_so_optional == opt_so_optional
|
||||
);
|
||||
if(!force_recompile && existing != context->server->units.end() && cache_mode_matches && !shared_unit_cache_is_stale(context, existing->second))
|
||||
return(existing->second);
|
||||
auto cached = compiler_reusable_cached_unit(context, file_name, opt_so_optional, force_recompile);
|
||||
if(cached)
|
||||
return(cached);
|
||||
|
||||
if(existing != context->server->units.end())
|
||||
release_shared_unit_cache_entry(context, file_name);
|
||||
compiler_release_cached_unit_if_needed(context, file_name, opt_so_optional, force_recompile);
|
||||
|
||||
SharedUnit* su = new SharedUnit();
|
||||
setup_unit_paths(context, su, file_name);
|
||||
@@ -598,38 +798,61 @@ SharedUnit* compiler_get_shared_unit_internal(Request* context, String file_name
|
||||
if(fdlock != -1)
|
||||
flock(fdlock, LOCK_EX);
|
||||
|
||||
existing = context->server->units.find(file_name);
|
||||
cache_mode_matches = (
|
||||
existing != context->server->units.end() &&
|
||||
existing->second->opt_so_optional == opt_so_optional
|
||||
);
|
||||
if(existing != context->server->units.end() && (force_recompile || !cache_mode_matches || shared_unit_cache_is_stale(context, existing->second)))
|
||||
release_shared_unit_cache_entry(context, file_name);
|
||||
existing = context->server->units.find(file_name);
|
||||
cache_mode_matches = (
|
||||
existing != context->server->units.end() &&
|
||||
existing->second->opt_so_optional == opt_so_optional
|
||||
);
|
||||
if(!force_recompile && existing != context->server->units.end() && cache_mode_matches && !shared_unit_cache_is_stale(context, existing->second))
|
||||
cached = compiler_reusable_cached_unit(context, file_name, opt_so_optional, force_recompile);
|
||||
if(cached)
|
||||
{
|
||||
compiler_close_lock_file(fdlock);
|
||||
delete su;
|
||||
return(existing->second);
|
||||
return(cached);
|
||||
}
|
||||
|
||||
compiler_release_cached_unit_if_needed(context, file_name, opt_so_optional, force_recompile);
|
||||
|
||||
auto state = inspect_shared_unit_filesystem(context, su);
|
||||
bool do_recompile = force_recompile || !state.source_exists || state.compiled_time == 0 || state.compiled_time < state.required_time;
|
||||
auto compile_check = shared_unit_compile_check(state);
|
||||
bool retry_deferred = compiler_failure_retry_deferred(context, su, state);
|
||||
bool jit_enabled = compiler_jit_compile_on_request_enabled(context);
|
||||
bool do_recompile = force_recompile || compile_check.needs_compile;
|
||||
if(do_recompile)
|
||||
{
|
||||
compile_shared_unit(context, su, file_name);
|
||||
if(!force_recompile && retry_deferred)
|
||||
compiler_restore_persisted_failure(su, state);
|
||||
else if(!force_recompile && !jit_enabled)
|
||||
{
|
||||
compiler_restore_persisted_failure(su, state, "jit_compile_disabled");
|
||||
if(trim(su->compiler_messages) == "")
|
||||
{
|
||||
su->compiler_messages = "JIT compilation on request is disabled";
|
||||
su->compile_error_status = su->compiler_messages;
|
||||
}
|
||||
}
|
||||
else
|
||||
compile_shared_unit(context, su);
|
||||
}
|
||||
else
|
||||
{
|
||||
load_shared_unit(context, su, file_name);
|
||||
load_shared_unit(context, su);
|
||||
if(!su->so_handle)
|
||||
compile_shared_unit(context, su, file_name);
|
||||
{
|
||||
if(!force_recompile && retry_deferred)
|
||||
compiler_restore_persisted_failure(su, state);
|
||||
else if(!force_recompile && !jit_enabled)
|
||||
{
|
||||
compiler_restore_persisted_failure(su, state, "jit_compile_disabled");
|
||||
if(trim(su->compiler_messages) == "")
|
||||
{
|
||||
su->compiler_messages = "JIT compilation on request is disabled";
|
||||
su->compile_error_status = su->compiler_messages;
|
||||
}
|
||||
}
|
||||
else
|
||||
compile_shared_unit(context, su);
|
||||
}
|
||||
}
|
||||
|
||||
auto observed_state = inspect_shared_unit_filesystem(context, su);
|
||||
compiler_record_observed_filesystem_state(su, observed_state);
|
||||
|
||||
compiler_close_lock_file(fdlock);
|
||||
|
||||
context->server->units[file_name] = su;
|
||||
@@ -728,13 +951,9 @@ StringList compiler_scan_site_units(Request* context)
|
||||
|
||||
StringList compiler_list_known_units(Request* context)
|
||||
{
|
||||
auto files = compiler_with_registry_lock(context, [&]() {
|
||||
return(compiler_with_registry_lock(context, [&]() {
|
||||
return(compiler_read_known_units_unlocked(context));
|
||||
});
|
||||
context->server->known_unit_files.clear();
|
||||
for(auto& file_name : files)
|
||||
context->server->known_unit_files[file_name] = true;
|
||||
return(files);
|
||||
}));
|
||||
}
|
||||
|
||||
void compiler_set_known_units(Request* context, StringList files)
|
||||
@@ -744,24 +963,22 @@ void compiler_set_known_units(Request* context, StringList files)
|
||||
compiler_write_known_units_unlocked(context, files);
|
||||
return(0);
|
||||
});
|
||||
context->server->known_unit_files.clear();
|
||||
for(auto& file_name : files)
|
||||
context->server->known_unit_files[file_name] = true;
|
||||
}
|
||||
|
||||
void compiler_track_known_unit(Request* context, String file_name)
|
||||
{
|
||||
file_name = compiler_normalize_unit_path(context, file_name);
|
||||
if(file_name == "" || !compiler_is_known_unit_file(file_name) || context->server->known_unit_files[file_name])
|
||||
if(file_name == "" || !compiler_is_known_unit_file(file_name))
|
||||
return;
|
||||
|
||||
compiler_with_registry_lock(context, [&]() {
|
||||
auto files = compiler_read_known_units_unlocked(context);
|
||||
if(std::find(files.begin(), files.end(), file_name) != files.end())
|
||||
return(0);
|
||||
files.push_back(file_name);
|
||||
compiler_write_known_units_unlocked(context, files);
|
||||
return(0);
|
||||
});
|
||||
context->server->known_unit_files[file_name] = true;
|
||||
}
|
||||
|
||||
void compiler_untrack_known_unit(Request* context, String file_name)
|
||||
@@ -776,7 +993,6 @@ void compiler_untrack_known_unit(Request* context, String file_name)
|
||||
compiler_write_known_units_unlocked(context, files);
|
||||
return(0);
|
||||
});
|
||||
context->server->known_unit_files.erase(file_name);
|
||||
}
|
||||
|
||||
bool compiler_unit_needs_recompile(Request* context, String file_name, bool* source_missing)
|
||||
@@ -785,13 +1001,14 @@ bool compiler_unit_needs_recompile(Request* context, String file_name, bool* sou
|
||||
SharedUnit su;
|
||||
setup_unit_paths(context, &su, file_name);
|
||||
auto state = inspect_shared_unit_filesystem(context, &su);
|
||||
auto compile_check = shared_unit_compile_check(state);
|
||||
if(source_missing)
|
||||
*source_missing = !state.source_exists;
|
||||
if(!state.source_exists)
|
||||
*source_missing = compile_check.source_missing;
|
||||
if(compile_check.source_missing)
|
||||
return(false);
|
||||
if(state.compiled_time == 0)
|
||||
return(true);
|
||||
return(state.compiled_time < state.required_time);
|
||||
if(compiler_failure_retry_deferred(context, &su, state))
|
||||
return(false);
|
||||
return(compile_check.needs_compile);
|
||||
}
|
||||
|
||||
DTree unit_info(String path)
|
||||
@@ -825,6 +1042,8 @@ DTree unit_info(String path)
|
||||
}
|
||||
|
||||
auto fs_state = inspect_shared_unit_filesystem(context, su);
|
||||
if(su->compile_status == "unknown" && compiler_failure_retry_deferred(context, su, fs_state))
|
||||
compiler_restore_persisted_failure(su, fs_state);
|
||||
auto exports_text = compiler_unit_exports_text(su);
|
||||
auto exports = compiler_unit_exports(su);
|
||||
if(exports.size() == 0 && exports_text != "")
|
||||
@@ -841,6 +1060,7 @@ DTree unit_info(String path)
|
||||
info["so_name"] = su->so_name;
|
||||
info["api_file_name"] = su->api_file_name;
|
||||
info["meta_file_name"] = su->meta_file_name;
|
||||
info["compile_output_file_name"] = su->compile_output_file_name;
|
||||
info["compile_status"] = (su->compile_status != "unknown" ? su->compile_status : compiler_status_from_filesystem(fs_state, su));
|
||||
info["compile_error_status"] = su->compile_error_status;
|
||||
info["runtime_error_status"] = su->runtime_error_status;
|
||||
@@ -867,11 +1087,15 @@ DTree unit_info(String path)
|
||||
info["source_mtime"] = (f64)fs_state.source_time;
|
||||
info["compiled_mtime"] = (f64)fs_state.compiled_time;
|
||||
info["metadata_mtime"] = (f64)fs_state.metadata_time;
|
||||
info["compile_output_mtime"] = (f64)fs_state.compile_output_time;
|
||||
info["setup_template_mtime"] = (f64)fs_state.setup_template_time;
|
||||
info["required_mtime"] = (f64)fs_state.required_time;
|
||||
info["runtime_abi_version"] = (f64)fs_state.runtime_abi_version;
|
||||
info["metadata_abi_version"] = (f64)fs_state.metadata_abi_version;
|
||||
compiler_tree_set_bool(info, "known", context->server->known_unit_files[resolved_path]);
|
||||
info["current_input_signature"] = fs_state.current_input_signature;
|
||||
info["metadata_input_signature"] = fs_state.metadata_input_signature;
|
||||
info["metadata_build_token"] = fs_state.metadata_build_token;
|
||||
compiler_tree_set_bool(info, "known", compiler_has_known_unit_cached(context, resolved_path));
|
||||
compiler_tree_set_bool(info, "current_unit", resolved_path == compiler_current_unit_path(context));
|
||||
compiler_tree_set_bool(info, "loaded", su->so_handle != 0);
|
||||
compiler_tree_set_bool(info, "source_exists", fs_state.source_exists);
|
||||
@@ -879,7 +1103,10 @@ DTree unit_info(String path)
|
||||
compiler_tree_set_bool(info, "metadata_exists", fs_state.metadata_exists);
|
||||
compiler_tree_set_bool(info, "metadata_parsed", fs_state.metadata_parsed);
|
||||
compiler_tree_set_bool(info, "abi_compatible", fs_state.abi_compatible);
|
||||
compiler_tree_set_bool(info, "stale", shared_unit_cache_is_stale(context, su));
|
||||
compiler_tree_set_bool(info, "input_signature_matches", fs_state.input_signature_matches);
|
||||
compiler_tree_set_bool(info, "stale", shared_unit_compile_check(fs_state).needs_compile && !compiler_failure_retry_deferred(context, su, fs_state));
|
||||
compiler_tree_set_bool(info, "retry_deferred", compiler_failure_retry_deferred(context, su, fs_state));
|
||||
compiler_tree_set_bool(info, "cache_stale", shared_unit_cache_is_stale(context, su));
|
||||
compiler_tree_set_bool(info, "has_render", su->on_render != 0);
|
||||
compiler_tree_set_bool(info, "has_component", su->on_component != 0);
|
||||
compiler_tree_set_bool(info, "has_websocket", su->on_websocket != 0);
|
||||
|
||||
+2
-2
@@ -7,8 +7,8 @@
|
||||
|
||||
String preprocess_shared_unit(Request* context, SharedUnit* su);
|
||||
void setup_unit_paths(Request* context, SharedUnit* su, String file_name);
|
||||
void load_shared_unit(Request* context, SharedUnit* su, String file_name);
|
||||
void compile_shared_unit(Request* context, SharedUnit* su, String file_name);
|
||||
void load_shared_unit(Request* context, SharedUnit* su);
|
||||
void compile_shared_unit(Request* context, SharedUnit* su);
|
||||
SharedUnit* get_shared_unit(Request* context, String file_name, bool opt_so_optional = false);
|
||||
void compiler_invoke(Request* context, String file_name);
|
||||
void compiler_invoke_websocket(Request* context, String file_name);
|
||||
|
||||
+296
-4
@@ -1,4 +1,8 @@
|
||||
|
||||
#include <cctype>
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
|
||||
namespace {
|
||||
|
||||
template <typename TreePtr>
|
||||
@@ -30,6 +34,110 @@ bool dtree_key_is_index(String key, s64 expected_index = -1)
|
||||
return(true);
|
||||
}
|
||||
|
||||
String dtree_trim(String raw)
|
||||
{
|
||||
if(raw == "")
|
||||
return(raw);
|
||||
|
||||
size_t start = 0;
|
||||
while(start < raw.length() && isspace(raw[start]))
|
||||
start += 1;
|
||||
|
||||
size_t end = raw.length();
|
||||
while(end > start && isspace(raw[end - 1]))
|
||||
end -= 1;
|
||||
|
||||
return(raw.substr(start, end - start));
|
||||
}
|
||||
|
||||
String dtree_lower(String raw)
|
||||
{
|
||||
for(auto& c : raw)
|
||||
c = (char)tolower(c);
|
||||
return(raw);
|
||||
}
|
||||
|
||||
bool dtree_string_to_bool_value(String raw, bool& value_out)
|
||||
{
|
||||
raw = dtree_lower(dtree_trim(raw));
|
||||
if(raw == "")
|
||||
return(false);
|
||||
if(raw == "1" || raw == "true" || raw == "(true)" || raw == "yes" || raw == "on")
|
||||
{
|
||||
value_out = true;
|
||||
return(true);
|
||||
}
|
||||
if(raw == "0" || raw == "false" || raw == "(false)" || raw == "no" || raw == "off" || raw == "null")
|
||||
{
|
||||
value_out = false;
|
||||
return(true);
|
||||
}
|
||||
return(false);
|
||||
}
|
||||
|
||||
bool dtree_string_to_f64_value(String raw, f64& value_out)
|
||||
{
|
||||
raw = dtree_trim(raw);
|
||||
if(raw == "")
|
||||
return(false);
|
||||
|
||||
bool bool_value = false;
|
||||
if(dtree_string_to_bool_value(raw, bool_value))
|
||||
{
|
||||
value_out = (bool_value ? 1.0 : 0.0);
|
||||
return(true);
|
||||
}
|
||||
|
||||
char* end = 0;
|
||||
value_out = strtod(raw.c_str(), &end);
|
||||
if(end == raw.c_str())
|
||||
return(false);
|
||||
while(end && *end != 0)
|
||||
{
|
||||
if(!isspace(*end))
|
||||
return(false);
|
||||
end += 1;
|
||||
}
|
||||
return(std::isfinite(value_out));
|
||||
}
|
||||
|
||||
const DTree* dtree_scalar_map_value(const DTree& tree)
|
||||
{
|
||||
if(tree.type != 'M' || tree._map.size() != 1)
|
||||
return(0);
|
||||
auto it = tree._map.begin();
|
||||
if(it == tree._map.end())
|
||||
return(0);
|
||||
return(&it->second.deref());
|
||||
}
|
||||
|
||||
f64 dtree_clamp_to_f64_range(long double value)
|
||||
{
|
||||
if(value > std::numeric_limits<f64>::max())
|
||||
return(std::numeric_limits<f64>::max());
|
||||
if(value < -std::numeric_limits<f64>::max())
|
||||
return(-std::numeric_limits<f64>::max());
|
||||
return((f64)value);
|
||||
}
|
||||
|
||||
s64 dtree_clamp_to_s64_range(long double value)
|
||||
{
|
||||
if(value > (long double)std::numeric_limits<s64>::max())
|
||||
return(std::numeric_limits<s64>::max());
|
||||
if(value < (long double)std::numeric_limits<s64>::min())
|
||||
return(std::numeric_limits<s64>::min());
|
||||
return((s64)value);
|
||||
}
|
||||
|
||||
u64 dtree_clamp_to_u64_range(long double value)
|
||||
{
|
||||
if(value <= 0)
|
||||
return(0);
|
||||
if(value > (long double)std::numeric_limits<u64>::max())
|
||||
return(std::numeric_limits<u64>::max());
|
||||
return((u64)value);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void DTree::each(std::function <void (DTree t, String key)> f)
|
||||
@@ -98,6 +206,158 @@ String DTree::to_string()
|
||||
return("");
|
||||
}
|
||||
|
||||
s64 DTree::to_s64()
|
||||
{
|
||||
const DTree& target = deref();
|
||||
switch(target.type)
|
||||
{
|
||||
case('S'):
|
||||
{
|
||||
f64 value = 0;
|
||||
if(!dtree_string_to_f64_value(target._String, value))
|
||||
return(0);
|
||||
return(dtree_clamp_to_s64_range((long double)value));
|
||||
}
|
||||
case('F'):
|
||||
return(dtree_clamp_to_s64_range((long double)target._float));
|
||||
case('B'):
|
||||
return(target._bool ? 1 : 0);
|
||||
case('M'):
|
||||
{
|
||||
const DTree* item = dtree_scalar_map_value(target);
|
||||
if(item)
|
||||
return(const_cast<DTree*>(item)->to_s64());
|
||||
return(0);
|
||||
}
|
||||
case('P'):
|
||||
return(dtree_clamp_to_s64_range((long double)(u64)target._ptr));
|
||||
case('R'):
|
||||
return(0);
|
||||
}
|
||||
return(0);
|
||||
}
|
||||
|
||||
u64 DTree::to_u64()
|
||||
{
|
||||
const DTree& target = deref();
|
||||
switch(target.type)
|
||||
{
|
||||
case('S'):
|
||||
{
|
||||
f64 value = 0;
|
||||
if(!dtree_string_to_f64_value(target._String, value))
|
||||
return(0);
|
||||
return(dtree_clamp_to_u64_range((long double)value));
|
||||
}
|
||||
case('F'):
|
||||
return(dtree_clamp_to_u64_range((long double)target._float));
|
||||
case('B'):
|
||||
return(target._bool ? 1 : 0);
|
||||
case('M'):
|
||||
{
|
||||
const DTree* item = dtree_scalar_map_value(target);
|
||||
if(item)
|
||||
return(const_cast<DTree*>(item)->to_u64());
|
||||
return(0);
|
||||
}
|
||||
case('P'):
|
||||
return((u64)target._ptr);
|
||||
case('R'):
|
||||
return(0);
|
||||
}
|
||||
return(0);
|
||||
}
|
||||
|
||||
f64 DTree::to_f64()
|
||||
{
|
||||
const DTree& target = deref();
|
||||
switch(target.type)
|
||||
{
|
||||
case('S'):
|
||||
{
|
||||
f64 value = 0;
|
||||
if(!dtree_string_to_f64_value(target._String, value))
|
||||
return(0);
|
||||
return(value);
|
||||
}
|
||||
case('F'):
|
||||
return(target._float);
|
||||
case('B'):
|
||||
return(target._bool ? 1.0 : 0.0);
|
||||
case('M'):
|
||||
{
|
||||
const DTree* item = dtree_scalar_map_value(target);
|
||||
if(item)
|
||||
return(const_cast<DTree*>(item)->to_f64());
|
||||
return(0);
|
||||
}
|
||||
case('P'):
|
||||
return(dtree_clamp_to_f64_range((long double)(u64)target._ptr));
|
||||
case('R'):
|
||||
return(0);
|
||||
}
|
||||
return(0);
|
||||
}
|
||||
|
||||
bool DTree::to_bool()
|
||||
{
|
||||
const DTree& target = deref();
|
||||
switch(target.type)
|
||||
{
|
||||
case('S'):
|
||||
{
|
||||
bool value = false;
|
||||
if(dtree_string_to_bool_value(target._String, value))
|
||||
return(value);
|
||||
f64 numeric_value = 0;
|
||||
if(dtree_string_to_f64_value(target._String, numeric_value))
|
||||
return(numeric_value != 0);
|
||||
return(dtree_trim(target._String) != "");
|
||||
}
|
||||
case('F'):
|
||||
return(target._float != 0);
|
||||
case('B'):
|
||||
return(target._bool);
|
||||
case('M'):
|
||||
{
|
||||
const DTree* item = dtree_scalar_map_value(target);
|
||||
if(item)
|
||||
return(const_cast<DTree*>(item)->to_bool());
|
||||
return(target._map.size() > 0);
|
||||
}
|
||||
case('P'):
|
||||
return(target._ptr != 0);
|
||||
case('R'):
|
||||
return(false);
|
||||
}
|
||||
return(false);
|
||||
}
|
||||
|
||||
StringMap DTree::to_stringmap()
|
||||
{
|
||||
const DTree& target = deref();
|
||||
StringMap result;
|
||||
switch(target.type)
|
||||
{
|
||||
case('M'):
|
||||
for(const auto& entry : target._map)
|
||||
result[entry.first] = const_cast<DTree&>(entry.second.deref()).to_string();
|
||||
break;
|
||||
case('S'):
|
||||
if(dtree_trim(target._String) != "")
|
||||
result["value"] = target._String;
|
||||
break;
|
||||
case('F'):
|
||||
case('B'):
|
||||
case('P'):
|
||||
result["value"] = const_cast<DTree&>(target).to_string();
|
||||
break;
|
||||
case('R'):
|
||||
break;
|
||||
}
|
||||
return(result);
|
||||
}
|
||||
|
||||
String DTree::to_json(char quote_char)
|
||||
{
|
||||
const DTree& target = deref();
|
||||
@@ -350,6 +610,7 @@ void DTree::set(StringMap source)
|
||||
return;
|
||||
}
|
||||
set_type('M');
|
||||
_map.clear();
|
||||
_array_index = 0;
|
||||
_list_mode = false;
|
||||
for (auto it = source.begin(); it != source.end(); ++it)
|
||||
@@ -378,12 +639,46 @@ void DTree::set_reference(DTree* target)
|
||||
_ptr = target;
|
||||
}
|
||||
|
||||
bool DTree::has(String s) const
|
||||
{
|
||||
const DTree& target = deref();
|
||||
if(target.type != 'M')
|
||||
return(false);
|
||||
return(target._map.find(s) != target._map.end());
|
||||
}
|
||||
|
||||
DTree* DTree::key(String s)
|
||||
{
|
||||
DTree* target = reference_target();
|
||||
if(target)
|
||||
return(target->key(s));
|
||||
if(type != 'M')
|
||||
return(0);
|
||||
auto it = _map.find(s);
|
||||
if(it == _map.end())
|
||||
return(0);
|
||||
return(&it->second);
|
||||
}
|
||||
|
||||
const DTree* DTree::key(String s) const
|
||||
{
|
||||
const DTree& target = deref();
|
||||
if(target.type != 'M')
|
||||
return(0);
|
||||
auto it = target._map.find(s);
|
||||
if(it == target._map.end())
|
||||
return(0);
|
||||
return(&it->second);
|
||||
}
|
||||
|
||||
DTree* DTree::get_or_create(String s)
|
||||
{
|
||||
DTree* target = reference_target();
|
||||
if(target)
|
||||
return(target->get_or_create(s));
|
||||
set_type('M');
|
||||
if(_list_mode && !dtree_key_is_index(s))
|
||||
_list_mode = false;
|
||||
return(&_map[s]);
|
||||
}
|
||||
|
||||
@@ -391,10 +686,7 @@ DTree& DTree::operator [] (String s) {
|
||||
DTree* target = reference_target();
|
||||
if(target)
|
||||
return((*target)[s]);
|
||||
set_type('M');
|
||||
if(_list_mode && !dtree_key_is_index(s))
|
||||
_list_mode = false;
|
||||
return(_map[s]);
|
||||
return(*get_or_create(s));
|
||||
}
|
||||
|
||||
void DTree::operator = (String v) { set(v); }
|
||||
|
||||
@@ -2,6 +2,11 @@
|
||||
|
||||
String json_escape(String s, char quote_char = '"');
|
||||
|
||||
// DTree is UCE's general-purpose structured value container.
|
||||
// It stores scalar values, nested map/list-like values, and internal references.
|
||||
// Numeric and boolean reads are intentionally permissive so request data,
|
||||
// JSON-decoded values, and metadata trees can be consumed without repetitive
|
||||
// manual parsing at each call site.
|
||||
struct DTree {
|
||||
|
||||
char type = 'S';
|
||||
@@ -18,6 +23,11 @@ struct DTree {
|
||||
bool is_array();
|
||||
bool is_list() const;
|
||||
String to_string();
|
||||
s64 to_s64();
|
||||
u64 to_u64();
|
||||
f64 to_f64();
|
||||
bool to_bool();
|
||||
StringMap to_stringmap();
|
||||
String to_json(char quote_char = '"');
|
||||
String get_type_name();
|
||||
DTree get_by_path(String path, String delim = "/");
|
||||
@@ -35,7 +45,10 @@ struct DTree {
|
||||
void set(StringMap source);
|
||||
void set_array();
|
||||
void set_reference(DTree* target);
|
||||
bool has(String s) const;
|
||||
DTree* key(String s);
|
||||
const DTree* key(String s) const;
|
||||
DTree* get_or_create(String s);
|
||||
DTree& operator [] (String s);
|
||||
void operator = (String v);
|
||||
void operator = (f64 v);
|
||||
|
||||
+166
-54
@@ -31,7 +31,7 @@ String capture_backtrace_string(u32 max_frames, u32 skip_frames)
|
||||
trace += "\n";
|
||||
}
|
||||
free(symbols);
|
||||
return(trace);
|
||||
return(trace);
|
||||
}
|
||||
|
||||
String signal_name(int sig)
|
||||
@@ -49,6 +49,40 @@ String signal_name(int sig)
|
||||
}
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
String time_format_expand_delta_tokens(String format, u64 timestamp, u64 now_timestamp)
|
||||
{
|
||||
u64 delta_seconds = 0;
|
||||
if(now_timestamp > timestamp)
|
||||
delta_seconds = now_timestamp - timestamp;
|
||||
|
||||
format = replace(format, "%deltaY", std::to_string(delta_seconds / (60 * 60 * 24 * 365)));
|
||||
format = replace(format, "%deltam", std::to_string(delta_seconds / (60 * 60 * 24 * 30)));
|
||||
format = replace(format, "%deltad", std::to_string(delta_seconds / (60 * 60 * 24)));
|
||||
format = replace(format, "%deltaH", std::to_string(delta_seconds / (60 * 60)));
|
||||
format = replace(format, "%deltaM", std::to_string(delta_seconds / 60));
|
||||
format = replace(format, "%deltaS", std::to_string(delta_seconds));
|
||||
return(format);
|
||||
}
|
||||
|
||||
String time_format_shell(String format, u64 timestamp, bool use_utc)
|
||||
{
|
||||
String ts;
|
||||
String fmt;
|
||||
u64 effective_timestamp = (timestamp > 0 ? timestamp : time());
|
||||
String expanded_format = time_format_expand_delta_tokens(format, effective_timestamp, time());
|
||||
|
||||
if(timestamp > 0)
|
||||
ts = String("-d '@")+std::to_string(timestamp)+"'";
|
||||
if(expanded_format != "")
|
||||
fmt = String("+'")+expanded_format+"'";
|
||||
|
||||
return(trim(shell_exec(String("date ") + (use_utc ? "-u " : "") + ts + " " + fmt)));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
String shell_exec(String cmd)
|
||||
{
|
||||
//printf("(i) shell_exec(%s)\n", cmd.c_str());
|
||||
@@ -154,63 +188,117 @@ bool file_exists(String path)
|
||||
return(std::filesystem::exists(fp));
|
||||
}
|
||||
|
||||
String file_get_contents(String file_name)
|
||||
int file_open_locked(String file_name, int open_flags, int lock_type, int create_mode)
|
||||
{
|
||||
/*std::ifstream ifs(file_name);
|
||||
printf("stream file desc %i\n", ifs.filedesc());
|
||||
String content(
|
||||
(std::istreambuf_iterator<char>(ifs) ),
|
||||
(std::istreambuf_iterator<char>() ) );*/
|
||||
int fd = open(file_name.c_str(), open_flags, create_mode);
|
||||
if(fd == -1)
|
||||
return(-1);
|
||||
if(flock(fd, lock_type) == -1)
|
||||
{
|
||||
close(fd);
|
||||
return(-1);
|
||||
}
|
||||
return(fd);
|
||||
}
|
||||
|
||||
void file_close_locked(int fd)
|
||||
{
|
||||
if(fd == -1)
|
||||
return;
|
||||
flock(fd, LOCK_UN);
|
||||
close(fd);
|
||||
}
|
||||
|
||||
String file_get_contents_locked_fd(int fd)
|
||||
{
|
||||
if(fd == -1)
|
||||
return("");
|
||||
|
||||
char buf[512];
|
||||
String content;
|
||||
s32 fd = open(file_name.c_str(), O_RDONLY);
|
||||
s64 bytes_read = 0;
|
||||
lseek(fd, 0, SEEK_SET);
|
||||
while((bytes_read = read(fd, buf, sizeof(buf))) > 0)
|
||||
content.append(buf, bytes_read);
|
||||
return(content);
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
bool file_write_all(int fd, const char* data, size_t remaining)
|
||||
{
|
||||
while(remaining > 0)
|
||||
{
|
||||
auto bytes_written = write(fd, data, remaining);
|
||||
if(bytes_written < 0)
|
||||
return(false);
|
||||
data += bytes_written;
|
||||
remaining -= bytes_written;
|
||||
}
|
||||
return(true);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
bool file_put_contents_locked_fd(int fd, String content)
|
||||
{
|
||||
if(fd == -1)
|
||||
return(false);
|
||||
lseek(fd, 0, SEEK_SET);
|
||||
if(ftruncate(fd, 0) != 0)
|
||||
return(false);
|
||||
if(!file_write_all(fd, content.data(), content.length()))
|
||||
return(false);
|
||||
return(true);
|
||||
}
|
||||
|
||||
String file_get_contents(String file_name)
|
||||
{
|
||||
s32 fd = file_open_locked(file_name, O_RDONLY, LOCK_SH);
|
||||
if(fd == -1)
|
||||
{
|
||||
printf("(!) Could not read %s\n", file_name.c_str());
|
||||
return("");
|
||||
}
|
||||
flock(fd, LOCK_SH);
|
||||
s64 bytes_read = 0;
|
||||
//s64 size = lseek(fd, 0, SEEK_END);
|
||||
//lseek(fd, 0, SEEK_SET);
|
||||
//content.reserve(size+1);
|
||||
|
||||
while((bytes_read = read(fd, buf, 512)) > 0)
|
||||
{
|
||||
content.append(buf, bytes_read);
|
||||
}
|
||||
|
||||
flock(fd, LOCK_UN);
|
||||
close(fd);
|
||||
String content = file_get_contents_locked_fd(fd);
|
||||
file_close_locked(fd);
|
||||
return(content);
|
||||
}
|
||||
|
||||
bool file_put_contents(String file_name, String content)
|
||||
{
|
||||
s32 fd = open(file_name.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0644);
|
||||
s32 fd = file_open_locked(file_name, O_RDWR | O_CREAT, LOCK_EX, 0644);
|
||||
if(fd == -1)
|
||||
{
|
||||
printf("(!) Could not write %s\n", file_name.c_str());
|
||||
return(false);
|
||||
}
|
||||
flock(fd, LOCK_EX);
|
||||
const char* data = content.data();
|
||||
size_t remaining = content.length();
|
||||
while(remaining > 0)
|
||||
bool ok = file_put_contents_locked_fd(fd, content);
|
||||
file_close_locked(fd);
|
||||
if(!ok)
|
||||
{
|
||||
auto bytes_written = write(fd, data, remaining);
|
||||
if(bytes_written < 0)
|
||||
{
|
||||
flock(fd, LOCK_UN);
|
||||
close(fd);
|
||||
printf("(!) Could not fully write %s\n", file_name.c_str());
|
||||
return(false);
|
||||
}
|
||||
data += bytes_written;
|
||||
remaining -= bytes_written;
|
||||
printf("(!) Could not fully write %s\n", file_name.c_str());
|
||||
return(false);
|
||||
}
|
||||
return(true);
|
||||
}
|
||||
|
||||
bool file_append_contents(String file_name, String content)
|
||||
{
|
||||
s32 fd = file_open_locked(file_name, O_RDWR | O_CREAT, LOCK_EX, 0644);
|
||||
if(fd == -1)
|
||||
{
|
||||
printf("(!) Could not append %s\n", file_name.c_str());
|
||||
return(false);
|
||||
}
|
||||
lseek(fd, 0, SEEK_END);
|
||||
bool ok = file_write_all(fd, content.data(), content.length());
|
||||
file_close_locked(fd);
|
||||
if(!ok)
|
||||
{
|
||||
printf("(!) Could not fully append %s\n", file_name.c_str());
|
||||
return(false);
|
||||
}
|
||||
flock(fd, LOCK_UN);
|
||||
close(fd);
|
||||
return(true);
|
||||
}
|
||||
|
||||
@@ -284,26 +372,34 @@ u64 time()
|
||||
|
||||
String time_format_local(String format, u64 timestamp)
|
||||
{
|
||||
String ts;
|
||||
String fmt;
|
||||
if(timestamp > 0)
|
||||
ts = String("-d '@")+std::to_string(timestamp)+"'";
|
||||
if(format != "")
|
||||
fmt = String("+'"+format+"'");
|
||||
return(trim(shell_exec("date "+ts+" "+fmt)));
|
||||
return(time_format_shell(format, timestamp, false));
|
||||
}
|
||||
|
||||
String time_format_utc(String format, u64 timestamp)
|
||||
{
|
||||
String ts;
|
||||
String fmt;
|
||||
if(timestamp > 0)
|
||||
ts = String("-d '@")+std::to_string(timestamp)+"'";
|
||||
if(format == "RFC1123")
|
||||
format = "%a, %d %b %Y %T GMT";
|
||||
if(format != "")
|
||||
fmt = String("+'"+format+"'");
|
||||
return(trim(shell_exec("date -u "+ts+" "+fmt)));
|
||||
return(time_format_shell(format, timestamp, true));
|
||||
}
|
||||
|
||||
String time_format_relative(u64 timestamp, String format_very_recent, u64 medium_recency_seconds, String format_medium_recent, u64 not_recent_seconds, String format_not_recent)
|
||||
{
|
||||
u64 now_timestamp = time();
|
||||
u64 delta_seconds = 0;
|
||||
if(now_timestamp > timestamp)
|
||||
delta_seconds = now_timestamp - timestamp;
|
||||
|
||||
format_very_recent = first(format_very_recent, "just now");
|
||||
medium_recency_seconds = (medium_recency_seconds > 0 ? medium_recency_seconds : 90);
|
||||
format_medium_recent = first(format_medium_recent, "%deltaM minutes ago");
|
||||
not_recent_seconds = (not_recent_seconds > 0 ? not_recent_seconds : 90 * 60);
|
||||
format_not_recent = first(format_not_recent, "%deltaH hours ago");
|
||||
|
||||
if(delta_seconds < medium_recency_seconds)
|
||||
return(time_format_expand_delta_tokens(format_very_recent, timestamp, now_timestamp));
|
||||
if(delta_seconds < not_recent_seconds)
|
||||
return(time_format_expand_delta_tokens(format_medium_recent, timestamp, now_timestamp));
|
||||
return(time_format_expand_delta_tokens(format_not_recent, timestamp, now_timestamp));
|
||||
}
|
||||
|
||||
u64 time_parse(String time_String)
|
||||
@@ -511,21 +607,29 @@ int task_kill(pid_t pid, int sig)
|
||||
pid_t task_pid(String key)
|
||||
{
|
||||
String status_file_name = context->server->config["BIN_DIRECTORY"] + "/task-" + key;
|
||||
String lock_file_name = status_file_name + ".lock";
|
||||
int lock_fd = file_open_locked(lock_file_name, O_RDWR | O_CREAT, LOCK_EX, 0644);
|
||||
String status_file = file_get_contents(status_file_name);
|
||||
pid_t p = 0;
|
||||
if(status_file != "")
|
||||
{
|
||||
p = int_val(status_file);
|
||||
if(task_kill(p, 0) == 0) // process is still running
|
||||
{
|
||||
file_close_locked(lock_fd);
|
||||
return(p);
|
||||
}
|
||||
file_unlink(status_file_name);
|
||||
}
|
||||
file_close_locked(lock_fd);
|
||||
return(p);
|
||||
}
|
||||
|
||||
pid_t task(String key, std::function<void()> exec_after_spawn, u64 timeout)
|
||||
{
|
||||
String status_file_name = context->server->config["BIN_DIRECTORY"] + "/task-" + key;
|
||||
String lock_file_name = status_file_name + ".lock";
|
||||
int lock_fd = file_open_locked(lock_file_name, O_RDWR | O_CREAT, LOCK_EX, 0644);
|
||||
String status_file = file_get_contents(status_file_name);
|
||||
pid_t p;
|
||||
if(status_file != "")
|
||||
@@ -534,6 +638,7 @@ pid_t task(String key, std::function<void()> exec_after_spawn, u64 timeout)
|
||||
if(task_kill(p, 0) == 0) // process is still running
|
||||
{
|
||||
printf("(P) worker process '%s' already running: PID %i\n", key.c_str(), p);
|
||||
file_close_locked(lock_fd);
|
||||
return(p);
|
||||
}
|
||||
//printf("(P) worker process '%s' had crashed: PID %i\n", key.c_str(), p);
|
||||
@@ -542,27 +647,31 @@ pid_t task(String key, std::function<void()> exec_after_spawn, u64 timeout)
|
||||
p = fork();
|
||||
if(p == 0)
|
||||
{
|
||||
file_close_locked(lock_fd);
|
||||
my_pid = getpid();
|
||||
file_put_contents(status_file_name, std::to_string(my_pid));
|
||||
|
||||
close(context->resources.client_socket);
|
||||
context->resources.client_socket = 0;
|
||||
//printf("(C) child procress started, PID:%i\n", my_pid);
|
||||
//prctl(PR_SET_PDEATHSIG, SIGHUP);
|
||||
exec_after_spawn();
|
||||
int exit_lock_fd = file_open_locked(lock_file_name, O_RDWR | O_CREAT, LOCK_EX, 0644);
|
||||
file_unlink(status_file_name);
|
||||
file_close_locked(exit_lock_fd);
|
||||
printf("(P) worker process '%s' terminated: PID %i\n", key.c_str(), my_pid);
|
||||
exit(0);
|
||||
}
|
||||
else
|
||||
{
|
||||
file_put_contents(status_file_name, std::to_string(p));
|
||||
file_close_locked(lock_fd);
|
||||
printf("(P) worker process '%s' spawned: PID %i\n", key.c_str(), p);
|
||||
return(p);
|
||||
}
|
||||
}
|
||||
|
||||
#include <unistd.h>
|
||||
pid_t task_repeat(String key, u64 interval, std::function<void()> exec_after_spawn, u64 timeout)
|
||||
pid_t task_repeat(String key, f64 interval, std::function<void()> exec_after_spawn, u64 timeout)
|
||||
{
|
||||
auto repeater_function = [&]() {
|
||||
while (true)
|
||||
@@ -610,6 +719,9 @@ StringMap make_server_settings()
|
||||
cfg["COMPILER_SYS_PATH"] = ".";
|
||||
cfg["PRECOMPILE_FILES_IN"] = "";
|
||||
cfg["SITE_DIRECTORY"] = "site";
|
||||
cfg["JIT_COMPILE_ON_REQUEST"] = "1";
|
||||
cfg["PROACTIVE_COMPILE_ENABLED"] = "1";
|
||||
cfg["COMPILE_FAILURE_RETRY_SECONDS"] = std::to_string(10);
|
||||
cfg["PROACTIVE_COMPILE_CHECK_INTERVAL"] = std::to_string(60);
|
||||
|
||||
cfg["HTTP_PORT"] = std::to_string(8080);
|
||||
|
||||
+11
-6
@@ -1,6 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#include <sys/file.h>
|
||||
#include <signal.h>
|
||||
#include <sstream>
|
||||
|
||||
String shell_exec(String cmd);
|
||||
String shell_escape(String raw);
|
||||
@@ -9,17 +11,19 @@ String dirname(String fn);
|
||||
String path_join(String base, String child);
|
||||
bool mkdir(String path);
|
||||
bool file_exists(String path);
|
||||
int file_open_locked(String file_name, int open_flags, int lock_type = LOCK_SH, int create_mode = 0644);
|
||||
void file_close_locked(int fd);
|
||||
String file_get_contents_locked_fd(int fd);
|
||||
bool file_put_contents_locked_fd(int fd, String content);
|
||||
String file_get_contents(String file_name);
|
||||
bool file_put_contents(String file_name, String content);
|
||||
#include <fstream>
|
||||
bool file_append_contents(String file_name, String content);
|
||||
template <typename... Ts>
|
||||
bool file_append(String file_name, Ts... args)
|
||||
{
|
||||
std::ofstream fout;
|
||||
fout.open(file_name.c_str(), std::ios_base::app);
|
||||
((fout << args), ...);
|
||||
fout.close();
|
||||
return(true);
|
||||
std::ostringstream out;
|
||||
((out << args), ...);
|
||||
return(file_append_contents(file_name, out.str()));
|
||||
}
|
||||
String cwd_get();
|
||||
void cwd_set(String path);
|
||||
@@ -32,6 +36,7 @@ f64 time_precise();
|
||||
u64 time();
|
||||
String time_format_local(String format = "", u64 timestamp = 0);
|
||||
String time_format_utc(String format = "", u64 timestamp = 0);
|
||||
String time_format_relative(u64 timestamp, String format_very_recent = "", u64 medium_recency_seconds = 0, String format_medium_recent = "", u64 not_recent_seconds = 0, String format_not_recent = "");
|
||||
u64 time_parse(String time_String);
|
||||
|
||||
u64 socket_connect(String host, short port);
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#include <sys/file.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <iostream>
|
||||
@@ -97,6 +98,11 @@ Request::~Request()
|
||||
delete stream;
|
||||
ob_stack.clear();
|
||||
ob = 0;
|
||||
if(session_lock_fd != -1)
|
||||
{
|
||||
flock(session_lock_fd, LOCK_UN);
|
||||
close(session_lock_fd);
|
||||
}
|
||||
for(auto& sockfd : resources.sockets)
|
||||
close(sockfd);
|
||||
}
|
||||
|
||||
+6
-1
@@ -66,6 +66,7 @@ struct SharedUnit {
|
||||
String so_name;
|
||||
String api_file_name;
|
||||
String meta_file_name;
|
||||
String compile_output_file_name;
|
||||
String setup_file_name;
|
||||
StringList api_declarations;
|
||||
std::map<String, void*> api_functions;
|
||||
@@ -89,9 +90,11 @@ struct SharedUnit {
|
||||
String compile_error_status = "";
|
||||
String runtime_error_status = "";
|
||||
time_t last_compiled = 0;
|
||||
time_t observed_compiled_time = 0;
|
||||
time_t last_loaded = 0;
|
||||
time_t last_rendered = 0;
|
||||
time_t last_error = 0;
|
||||
String observed_metadata_content = "";
|
||||
|
||||
u64 request_count = 0;
|
||||
u64 invoke_count = 0;
|
||||
@@ -125,7 +128,6 @@ struct UploadedFile {
|
||||
struct ServerState {
|
||||
|
||||
std::map<String, SharedUnit*> units;
|
||||
std::map<String, bool> known_unit_files;
|
||||
StringMap config;
|
||||
u32 request_count = 0;
|
||||
|
||||
@@ -153,6 +155,9 @@ struct Request {
|
||||
StringMap post;
|
||||
StringMap cookies;
|
||||
StringMap session;
|
||||
String session_file_name = "";
|
||||
String session_serialized = "";
|
||||
int session_lock_fd = -1;
|
||||
|
||||
DTree var;
|
||||
DTree cfg;
|
||||
|
||||
+53
-1
@@ -542,27 +542,70 @@ String session_file_path(String session_id)
|
||||
return(context->server->config["SESSION_PATH"] + "/" + session_id);
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
void session_release_lock(Request* request)
|
||||
{
|
||||
if(!request)
|
||||
return;
|
||||
if(request->session_lock_fd != -1)
|
||||
file_close_locked(request->session_lock_fd);
|
||||
request->session_lock_fd = -1;
|
||||
request->session_file_name = "";
|
||||
request->session_serialized = "";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
StringMap load_session_data(String session_id)
|
||||
{
|
||||
String session_path = session_file_path(session_id);
|
||||
if(session_path == "")
|
||||
return(StringMap());
|
||||
if(context && context->session_lock_fd != -1 && context->session_file_name == session_path)
|
||||
return(parse_query(file_get_contents_locked_fd(context->session_lock_fd)));
|
||||
return(parse_query(file_get_contents(session_path)));
|
||||
}
|
||||
|
||||
void save_session_data(String session_id, StringMap data)
|
||||
{
|
||||
String session_path = session_file_path(session_id);
|
||||
String encoded = encode_query(data);
|
||||
if(session_path == "")
|
||||
{
|
||||
printf("(!) Refusing to save invalid session id\n");
|
||||
return;
|
||||
}
|
||||
file_put_contents(session_path, encode_query(data));
|
||||
if(context && context->session_lock_fd != -1 && context->session_file_name == session_path)
|
||||
{
|
||||
if(encoded == context->session_serialized)
|
||||
return;
|
||||
if(file_put_contents_locked_fd(context->session_lock_fd, encoded))
|
||||
context->session_serialized = encoded;
|
||||
return;
|
||||
}
|
||||
int fd = file_open_locked(session_path, O_RDWR | O_CREAT, LOCK_EX, 0644);
|
||||
if(fd == -1)
|
||||
{
|
||||
printf("(!) Refusing to save unreadable session file %s\n", session_path.c_str());
|
||||
return;
|
||||
}
|
||||
if(file_get_contents_locked_fd(fd) != encoded)
|
||||
file_put_contents_locked_fd(fd, encoded);
|
||||
file_close_locked(fd);
|
||||
}
|
||||
|
||||
String session_start(String session_name)
|
||||
{
|
||||
if(context->session_lock_fd != -1 && context->session_name == session_name && context->session_id != "")
|
||||
return(context->session_id);
|
||||
session_release_lock(context);
|
||||
context->session.clear();
|
||||
context->session_serialized = "";
|
||||
context->session_file_name = "";
|
||||
context->session_id = "";
|
||||
context->session_name = "";
|
||||
|
||||
String session_id = context->cookies[session_name];
|
||||
if(!is_valid_session_id(session_id))
|
||||
session_id = "";
|
||||
@@ -574,7 +617,15 @@ String session_start(String session_name)
|
||||
}
|
||||
context->session_id = session_id;
|
||||
context->session_name = session_name;
|
||||
context->session_file_name = session_file_path(context->session_id);
|
||||
if(context->session_file_name != "")
|
||||
{
|
||||
context->session_lock_fd = file_open_locked(context->session_file_name, O_RDWR | O_CREAT, LOCK_EX, 0644);
|
||||
if(context->session_lock_fd == -1)
|
||||
printf("(!) Could not lock session file %s\n", context->session_file_name.c_str());
|
||||
}
|
||||
context->session = load_session_data(context->session_id);
|
||||
context->session_serialized = encode_query(context->session);
|
||||
return(context->session_id);
|
||||
}
|
||||
|
||||
@@ -585,6 +636,7 @@ void session_destroy(String session_name)
|
||||
set_cookie(session_name, "", time() - int_val(context->server->config["SESSION_TIME"]));
|
||||
context->session.clear();
|
||||
save_session_data(context->session_id, context->session);
|
||||
session_release_lock(context);
|
||||
context->session_id = "";
|
||||
}
|
||||
}
|
||||
|
||||
+23
-2
@@ -145,6 +145,18 @@ String ws_message()
|
||||
return(context->call["message"].to_string());
|
||||
}
|
||||
|
||||
bool config_truthy(String raw, bool default_value = true)
|
||||
{
|
||||
raw = to_lower(trim(raw));
|
||||
if(raw == "")
|
||||
return(default_value);
|
||||
if(raw == "1" || raw == "true" || raw == "yes" || raw == "on")
|
||||
return(true);
|
||||
if(raw == "0" || raw == "false" || raw == "no" || raw == "off")
|
||||
return(false);
|
||||
return(default_value);
|
||||
}
|
||||
|
||||
String ws_connection_id()
|
||||
{
|
||||
if(!context)
|
||||
@@ -419,17 +431,22 @@ void run_proactive_compiler()
|
||||
{
|
||||
Request background_context;
|
||||
StringList compile_queue;
|
||||
background_context.server = &server_state;
|
||||
if(!config_truthy(server_state.config["PROACTIVE_COMPILE_ENABLED"], true))
|
||||
return;
|
||||
f64 check_interval = float_val(server_state.config["PROACTIVE_COMPILE_CHECK_INTERVAL"]);
|
||||
f64 failure_retry_interval = 0;
|
||||
f64 next_scan_at = 0;
|
||||
std::map<String, f64> retry_after;
|
||||
if(check_interval < 1)
|
||||
check_interval = 1;
|
||||
failure_retry_interval = std::max(check_interval, 60.0);
|
||||
failure_retry_interval = std::max(
|
||||
check_interval,
|
||||
(f64)std::max((s64)10, (s64)int_val(server_state.config["COMPILE_FAILURE_RETRY_SECONDS"]))
|
||||
);
|
||||
|
||||
my_pid = getpid();
|
||||
context = &background_context;
|
||||
background_context.server = &server_state;
|
||||
|
||||
close_inherited_server_sockets();
|
||||
signal(SIGSEGV, on_segfault);
|
||||
@@ -502,6 +519,8 @@ void run_proactive_compiler()
|
||||
{
|
||||
retry_after.erase(file_name);
|
||||
}
|
||||
background_context.session.clear();
|
||||
background_context.session_serialized = "";
|
||||
clear_shared_unit_cache(server_state);
|
||||
usleep(250000);
|
||||
continue;
|
||||
@@ -518,6 +537,8 @@ bool proactive_compiler_alive()
|
||||
|
||||
void ensure_proactive_compiler()
|
||||
{
|
||||
if(!config_truthy(server_state.config["PROACTIVE_COMPILE_ENABLED"], true))
|
||||
return;
|
||||
if(float_val(server_state.config["PROACTIVE_COMPILE_CHECK_INTERVAL"]) <= 0)
|
||||
return;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user