fix: stabilize runtime follow-up regressions
This commit is contained in:
+23
-21
@@ -345,7 +345,7 @@ bool compiler_line_starts_fragmentable_entrypoint(String trimmed, String& kind)
|
||||
|
||||
String compiler_fragment_capture_prelude(String slot)
|
||||
{
|
||||
return("\nstruct __UceFragmentCapture { Request& context; String slot; __UceFragmentCapture(Request& c, String s) : context(c), slot(s) { ob_start(); } ~__UceFragmentCapture() { String html = ob_get_close(); if(html != \"\") context.call[\"fragments\"][slot] = context.call[\"fragments\"][slot].to_string() + html; } }; __UceFragmentCapture __uce_fragment_capture(context, " + compiler_cpp_string_literal(slot) + ");\n");
|
||||
return("\nUceFragmentCapture __uce_fragment_capture(context, " + compiler_cpp_string_literal(slot) + ");\n");
|
||||
}
|
||||
|
||||
String compiler_rewrite_fragment_attributes(String content)
|
||||
@@ -365,7 +365,6 @@ String compiler_rewrite_fragment_attributes(String content)
|
||||
}
|
||||
|
||||
String slot = (kind == "ONCE" ? "once" : "");
|
||||
StringList pending;
|
||||
bool has_fragment_attr = false;
|
||||
u32 j = i + 1;
|
||||
while(j < lines.size())
|
||||
@@ -402,28 +401,31 @@ String compiler_rewrite_fragment_attributes(String content)
|
||||
continue;
|
||||
}
|
||||
|
||||
result += line;
|
||||
if(j < lines.size())
|
||||
result += "\n";
|
||||
|
||||
while(j < lines.size())
|
||||
// The body must open on the next non-blank line after the attribute
|
||||
// lines; anything else (e.g. literal text that merely starts with
|
||||
// "ONCE(") is not an entry point and passes through untouched.
|
||||
u32 body_index = j;
|
||||
while(body_index < lines.size() && trim(lines[body_index]) == "")
|
||||
body_index++;
|
||||
bool body_opens = body_index < lines.size() && trim(lines[body_index]).rfind("{", 0) == 0;
|
||||
if(!body_opens)
|
||||
{
|
||||
String body_line = lines[j];
|
||||
auto brace_pos = body_line.find("{");
|
||||
if(brace_pos == String::npos)
|
||||
{
|
||||
result += body_line;
|
||||
if(j + 1 < lines.size())
|
||||
result += "\n";
|
||||
j++;
|
||||
continue;
|
||||
}
|
||||
result += body_line.substr(0, brace_pos + 1) + compiler_fragment_capture_prelude(slot) + body_line.substr(brace_pos + 1);
|
||||
if(j + 1 < lines.size())
|
||||
result += line;
|
||||
if(i + 1 < lines.size())
|
||||
result += "\n";
|
||||
i = j;
|
||||
break;
|
||||
continue;
|
||||
}
|
||||
|
||||
result += line;
|
||||
result += "\n";
|
||||
for(u32 k = j; k < body_index; k++)
|
||||
result += lines[k] + "\n";
|
||||
String body_line = lines[body_index];
|
||||
auto brace_pos = body_line.find("{");
|
||||
result += body_line.substr(0, brace_pos + 1) + compiler_fragment_capture_prelude(slot) + body_line.substr(brace_pos + 1);
|
||||
if(body_index + 1 < lines.size())
|
||||
result += "\n";
|
||||
i = body_index;
|
||||
}
|
||||
return(result);
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ const char* UCE_CLI_SYMBOL = "__uce_cli";
|
||||
const char* UCE_SERVE_HTTP_SYMBOL = "__uce_serve_http";
|
||||
const char* UCE_ONCE_SYMBOL = "__uce_once";
|
||||
const char* UCE_INIT_SYMBOL = "__uce_init";
|
||||
const u64 UCE_UNIT_ABI_VERSION = 2;
|
||||
const u64 UCE_UNIT_ABI_VERSION = 4;
|
||||
|
||||
struct SharedUnitFilesystemState
|
||||
{
|
||||
|
||||
+71
-51
@@ -20,7 +20,7 @@ TreePtr dtree_resolve_reference(TreePtr tree)
|
||||
return(tree);
|
||||
}
|
||||
|
||||
bool dtree_key_is_index(String key, s64 expected_index = -1)
|
||||
bool dtree_key_is_index(String key)
|
||||
{
|
||||
if(key == "")
|
||||
return(false);
|
||||
@@ -29,8 +29,10 @@ bool dtree_key_is_index(String key, s64 expected_index = -1)
|
||||
if(!isdigit(c))
|
||||
return(false);
|
||||
}
|
||||
if(expected_index >= 0)
|
||||
return(key == std::to_string(expected_index));
|
||||
// Only canonical index strings count ("1", not "01"), so each numeric
|
||||
// value has exactly one representation.
|
||||
if(key.length() > 1 && key[0] == '0')
|
||||
return(false);
|
||||
return(true);
|
||||
}
|
||||
|
||||
@@ -140,12 +142,25 @@ u64 dtree_clamp_to_u64_range(long double value)
|
||||
|
||||
}
|
||||
|
||||
void DTree::each(std::function <void (const DTree& t, String key)> f)
|
||||
void DTree::each(std::function <void (const DTree& t, String key)> f) const
|
||||
{
|
||||
const DTree& target = deref();
|
||||
switch(target.type)
|
||||
{
|
||||
case('M'):
|
||||
// Lists iterate in numeric index order (string keys sort "10"
|
||||
// before "2"), matching the json/xml/yaml encoders.
|
||||
if(target.is_list())
|
||||
{
|
||||
for(u64 i = 0; i < target._map.size(); i++)
|
||||
{
|
||||
auto it = target._map.find(std::to_string(i));
|
||||
if(it == target._map.end())
|
||||
break;
|
||||
f(it->second, it->first);
|
||||
}
|
||||
break;
|
||||
}
|
||||
for (auto it = target._map.begin(); it != target._map.end(); ++it)
|
||||
{
|
||||
f(it->second, it->first);
|
||||
@@ -157,7 +172,7 @@ void DTree::each(std::function <void (const DTree& t, String key)> f)
|
||||
}
|
||||
}
|
||||
|
||||
bool DTree::is_array()
|
||||
bool DTree::is_array() const
|
||||
{
|
||||
return(deref().type == 'M');
|
||||
}
|
||||
@@ -169,44 +184,45 @@ bool DTree::is_list() const
|
||||
return(false);
|
||||
if(target._map.size() == 0)
|
||||
return(target._list_mode);
|
||||
s64 expected_index = 0;
|
||||
// The map iterates in string order ("10" before "2"), so the check must
|
||||
// be order-independent: n unique canonical index keys with maximum n-1
|
||||
// are exactly 0..n-1.
|
||||
s64 max_index = -1;
|
||||
for(const auto& entry : target._map)
|
||||
{
|
||||
if(!dtree_key_is_index(entry.first, expected_index))
|
||||
if(!dtree_key_is_index(entry.first))
|
||||
return(false);
|
||||
expected_index += 1;
|
||||
s64 index = strtoll(entry.first.c_str(), 0, 10);
|
||||
if(index > max_index)
|
||||
max_index = index;
|
||||
}
|
||||
return(true);
|
||||
return(max_index == (s64)target._map.size() - 1);
|
||||
}
|
||||
|
||||
String DTree::to_string()
|
||||
String DTree::to_string(String default_value) const
|
||||
{
|
||||
const DTree& target = deref();
|
||||
switch(target.type)
|
||||
{
|
||||
case('S'):
|
||||
if(target._String == "")
|
||||
return(default_value);
|
||||
return(target._String);
|
||||
break;
|
||||
case('F'):
|
||||
return(std::to_string(target._float));
|
||||
break;
|
||||
case('B'):
|
||||
return(target._bool ? "(true)" : "(false)");
|
||||
break;
|
||||
case('M'):
|
||||
return("");
|
||||
break;
|
||||
return(default_value);
|
||||
case('P'):
|
||||
return(std::to_string((u64)target._ptr));
|
||||
break;
|
||||
case('R'):
|
||||
return("");
|
||||
break;
|
||||
return(default_value);
|
||||
}
|
||||
return("");
|
||||
return(default_value);
|
||||
}
|
||||
|
||||
s64 DTree::to_s64()
|
||||
s64 DTree::to_s64(s64 default_value) const
|
||||
{
|
||||
const DTree& target = deref();
|
||||
switch(target.type)
|
||||
@@ -215,7 +231,7 @@ s64 DTree::to_s64()
|
||||
{
|
||||
f64 value = 0;
|
||||
if(!dtree_string_to_f64_value(target._String, value))
|
||||
return(0);
|
||||
return(default_value);
|
||||
return(dtree_clamp_to_s64_range((long double)value));
|
||||
}
|
||||
case('F'):
|
||||
@@ -226,18 +242,18 @@ s64 DTree::to_s64()
|
||||
{
|
||||
const DTree* item = dtree_scalar_map_value(target);
|
||||
if(item)
|
||||
return(const_cast<DTree*>(item)->to_s64());
|
||||
return(0);
|
||||
return(item->to_s64(default_value));
|
||||
return(default_value);
|
||||
}
|
||||
case('P'):
|
||||
return(dtree_clamp_to_s64_range((long double)(u64)target._ptr));
|
||||
case('R'):
|
||||
return(0);
|
||||
return(default_value);
|
||||
}
|
||||
return(0);
|
||||
return(default_value);
|
||||
}
|
||||
|
||||
u64 DTree::to_u64()
|
||||
u64 DTree::to_u64(u64 default_value) const
|
||||
{
|
||||
const DTree& target = deref();
|
||||
switch(target.type)
|
||||
@@ -246,7 +262,7 @@ u64 DTree::to_u64()
|
||||
{
|
||||
f64 value = 0;
|
||||
if(!dtree_string_to_f64_value(target._String, value))
|
||||
return(0);
|
||||
return(default_value);
|
||||
return(dtree_clamp_to_u64_range((long double)value));
|
||||
}
|
||||
case('F'):
|
||||
@@ -257,18 +273,18 @@ u64 DTree::to_u64()
|
||||
{
|
||||
const DTree* item = dtree_scalar_map_value(target);
|
||||
if(item)
|
||||
return(const_cast<DTree*>(item)->to_u64());
|
||||
return(0);
|
||||
return(item->to_u64(default_value));
|
||||
return(default_value);
|
||||
}
|
||||
case('P'):
|
||||
return((u64)target._ptr);
|
||||
case('R'):
|
||||
return(0);
|
||||
return(default_value);
|
||||
}
|
||||
return(0);
|
||||
return(default_value);
|
||||
}
|
||||
|
||||
f64 DTree::to_f64()
|
||||
f64 DTree::to_f64(f64 default_value) const
|
||||
{
|
||||
const DTree& target = deref();
|
||||
switch(target.type)
|
||||
@@ -277,7 +293,7 @@ f64 DTree::to_f64()
|
||||
{
|
||||
f64 value = 0;
|
||||
if(!dtree_string_to_f64_value(target._String, value))
|
||||
return(0);
|
||||
return(default_value);
|
||||
return(value);
|
||||
}
|
||||
case('F'):
|
||||
@@ -288,18 +304,18 @@ f64 DTree::to_f64()
|
||||
{
|
||||
const DTree* item = dtree_scalar_map_value(target);
|
||||
if(item)
|
||||
return(const_cast<DTree*>(item)->to_f64());
|
||||
return(0);
|
||||
return(item->to_f64(default_value));
|
||||
return(default_value);
|
||||
}
|
||||
case('P'):
|
||||
return(dtree_clamp_to_f64_range((long double)(u64)target._ptr));
|
||||
case('R'):
|
||||
return(0);
|
||||
return(default_value);
|
||||
}
|
||||
return(0);
|
||||
return(default_value);
|
||||
}
|
||||
|
||||
bool DTree::to_bool()
|
||||
bool DTree::to_bool(bool default_value) const
|
||||
{
|
||||
const DTree& target = deref();
|
||||
switch(target.type)
|
||||
@@ -312,7 +328,11 @@ bool DTree::to_bool()
|
||||
f64 numeric_value = 0;
|
||||
if(dtree_string_to_f64_value(target._String, numeric_value))
|
||||
return(numeric_value != 0);
|
||||
return(dtree_trim(target._String) != "");
|
||||
// Non-empty unparseable strings stay truthy; only a missing/empty
|
||||
// value falls back to the default.
|
||||
if(dtree_trim(target._String) != "")
|
||||
return(true);
|
||||
return(default_value);
|
||||
}
|
||||
case('F'):
|
||||
return(target._float != 0);
|
||||
@@ -322,18 +342,18 @@ bool DTree::to_bool()
|
||||
{
|
||||
const DTree* item = dtree_scalar_map_value(target);
|
||||
if(item)
|
||||
return(const_cast<DTree*>(item)->to_bool());
|
||||
return(item->to_bool(default_value));
|
||||
return(target._map.size() > 0);
|
||||
}
|
||||
case('P'):
|
||||
return(target._ptr != 0);
|
||||
case('R'):
|
||||
return(false);
|
||||
return(default_value);
|
||||
}
|
||||
return(false);
|
||||
return(default_value);
|
||||
}
|
||||
|
||||
StringMap DTree::to_stringmap()
|
||||
StringMap DTree::to_stringmap() const
|
||||
{
|
||||
const DTree& target = deref();
|
||||
StringMap result;
|
||||
@@ -341,7 +361,7 @@ StringMap DTree::to_stringmap()
|
||||
{
|
||||
case('M'):
|
||||
for(const auto& entry : target._map)
|
||||
result[entry.first] = const_cast<DTree&>(entry.second.deref()).to_string();
|
||||
result[entry.first] = entry.second.deref().to_string();
|
||||
break;
|
||||
case('S'):
|
||||
if(dtree_trim(target._String) != "")
|
||||
@@ -350,7 +370,7 @@ StringMap DTree::to_stringmap()
|
||||
case('F'):
|
||||
case('B'):
|
||||
case('P'):
|
||||
result["value"] = const_cast<DTree&>(target).to_string();
|
||||
result["value"] = target.to_string();
|
||||
break;
|
||||
case('R'):
|
||||
break;
|
||||
@@ -358,7 +378,7 @@ StringMap DTree::to_stringmap()
|
||||
return(result);
|
||||
}
|
||||
|
||||
String DTree::to_json(char quote_char)
|
||||
String DTree::to_json(char quote_char) const
|
||||
{
|
||||
const DTree& target = deref();
|
||||
switch(target.type)
|
||||
@@ -385,7 +405,7 @@ String DTree::to_json(char quote_char)
|
||||
return("\"(unknown)\"");
|
||||
}
|
||||
|
||||
String DTree::get_type_name()
|
||||
String DTree::get_type_name() const
|
||||
{
|
||||
const DTree& target = deref();
|
||||
switch(target.type)
|
||||
@@ -412,7 +432,7 @@ String DTree::get_type_name()
|
||||
return("unknown");
|
||||
}
|
||||
|
||||
DTree DTree::get_by_path(String path, String delim)
|
||||
DTree DTree::get_by_path(String path, String delim) const
|
||||
{
|
||||
const DTree* current = &deref();
|
||||
if(path == "")
|
||||
@@ -447,7 +467,7 @@ DTree DTree::get_by_path(String path, String delim)
|
||||
return(current->deref());
|
||||
}
|
||||
|
||||
bool DTree::is_reference()
|
||||
bool DTree::is_reference() const
|
||||
{
|
||||
return(type == 'R');
|
||||
}
|
||||
@@ -778,12 +798,12 @@ String to_String(DTree t)
|
||||
return(t.to_string());
|
||||
}
|
||||
|
||||
String var_dump(DTree map, String prefix, String postfix)
|
||||
String var_dump(const DTree& map, String prefix, String postfix)
|
||||
{
|
||||
String result = "";
|
||||
if(!map.is_array())
|
||||
return(map.to_string());
|
||||
map.each([&] (DTree item, String key) {
|
||||
map.each([&] (const DTree& item, String key) {
|
||||
result += prefix + key + ": " + item.to_string() + postfix;
|
||||
if(item.is_array())
|
||||
result += var_dump(item, prefix + "\t");
|
||||
|
||||
+16
-13
@@ -19,19 +19,22 @@ struct DTree {
|
||||
void* _ptr;
|
||||
std::map<String, DTree> _map;
|
||||
|
||||
void each(std::function <void (const DTree& t, String key)> f);
|
||||
bool is_array();
|
||||
// Read accessors are const and never create or modify nodes. The to_*
|
||||
// conversions take an optional default that is returned when the value is
|
||||
// missing (empty) or cannot be converted to the requested type.
|
||||
void each(std::function <void (const DTree& t, String key)> f) const;
|
||||
bool is_array() const;
|
||||
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 = "/");
|
||||
bool is_reference();
|
||||
String to_string(String default_value = "") const;
|
||||
s64 to_s64(s64 default_value = 0) const;
|
||||
u64 to_u64(u64 default_value = 0) const;
|
||||
f64 to_f64(f64 default_value = 0) const;
|
||||
bool to_bool(bool default_value = false) const;
|
||||
StringMap to_stringmap() const;
|
||||
String to_json(char quote_char = '"') const;
|
||||
String get_type_name() const;
|
||||
DTree get_by_path(String path, String delim = "/") const;
|
||||
bool is_reference() const;
|
||||
DTree* reference_target();
|
||||
const DTree* reference_target() const;
|
||||
DTree& deref();
|
||||
@@ -63,4 +66,4 @@ struct DTree {
|
||||
};
|
||||
|
||||
String to_String(DTree t);
|
||||
String var_dump(DTree map, String prefix = "", String postfix = "\n");
|
||||
String var_dump(const DTree& map, String prefix = "", String postfix = "\n");
|
||||
|
||||
+13
-1
@@ -745,7 +745,19 @@ StringMap split_http_headers(String s)
|
||||
while(header_start < lines.size() && trim(lines[header_start]) == "")
|
||||
header_start++;
|
||||
|
||||
if(header_start < lines.size() && lines[header_start].find(':') == String::npos)
|
||||
// A header line has its colon before any whitespace ("Host: x"); a request
|
||||
// line has a space first even when the URI contains colons
|
||||
// ("GET /x.uce?t=12:30 HTTP/1.1").
|
||||
bool first_line_is_header = false;
|
||||
if(header_start < lines.size())
|
||||
{
|
||||
String first_line = trim(lines[header_start]);
|
||||
size_t colon = first_line.find(':');
|
||||
size_t space = first_line.find_first_of(" \t");
|
||||
first_line_is_header = colon != String::npos && (space == String::npos || colon < space);
|
||||
}
|
||||
|
||||
if(header_start < lines.size() && !first_line_is_header)
|
||||
{
|
||||
String request_line = trim(lines[header_start]);
|
||||
result["REQUEST_METHOD"] = nibble(request_line, " ");
|
||||
|
||||
@@ -132,6 +132,26 @@ void ob_close();
|
||||
String ob_get();
|
||||
String ob_get_close();
|
||||
|
||||
// RAII capture used by the preprocessor's @fragment rewrite: buffers the
|
||||
// handler's output and appends it to context.call["fragments"][slot].
|
||||
struct UceFragmentCapture
|
||||
{
|
||||
Request& context;
|
||||
String slot;
|
||||
|
||||
UceFragmentCapture(Request& c, String s) : context(c), slot(s)
|
||||
{
|
||||
ob_start();
|
||||
}
|
||||
|
||||
~UceFragmentCapture()
|
||||
{
|
||||
String html = ob_get_close();
|
||||
if(html != "")
|
||||
context.call["fragments"][slot] = context.call["fragments"][slot].to_string() + html;
|
||||
}
|
||||
};
|
||||
|
||||
String safe_name(String raw);
|
||||
String ascii_safe_name(String raw);
|
||||
|
||||
|
||||
+29
-13
@@ -3,16 +3,28 @@
|
||||
#include <stdlib.h>
|
||||
#include "mysql-connector.h"
|
||||
|
||||
static void mysql_register_request_connection(MySQL* db)
|
||||
{
|
||||
if(!context || !db)
|
||||
return;
|
||||
auto& connections = context->resources.mysql_connections;
|
||||
if(std::find(connections.begin(), connections.end(), (void*)db) == connections.end())
|
||||
connections.push_back(db);
|
||||
}
|
||||
|
||||
bool MySQL::connect(String host, String username, String password)
|
||||
{
|
||||
// Register regardless of outcome: tracking is about the wrapper's
|
||||
// lifetime, not the connection's. disconnect()/~MySQL() unregister.
|
||||
mysql_register_request_connection(this);
|
||||
//switch_to_system_alloc();
|
||||
connection = mysql_init(NULL);
|
||||
if (connection == NULL)
|
||||
{
|
||||
auto e = mysql_error((MYSQL*)connection);
|
||||
fprintf(stderr, "%s\n", e);
|
||||
fprintf(stderr, "mysql_init failed\n");
|
||||
//switch_to_arena(context->mem);
|
||||
statement_info.assign(e);
|
||||
_preload_next_error_code = CR_OUT_OF_MEMORY;
|
||||
statement_info = "mysql_init failed";
|
||||
return(false);
|
||||
}
|
||||
|
||||
@@ -21,9 +33,11 @@ bool MySQL::connect(String host, String username, String password)
|
||||
{
|
||||
auto e = mysql_error((MYSQL*)connection);
|
||||
fprintf(stderr, "%s\n", e);
|
||||
mysql_close((MYSQL*)connection);
|
||||
//switch_to_arena(context->mem);
|
||||
_preload_next_error_code = CR_UNKNOWN_ERROR;
|
||||
statement_info.assign(e);
|
||||
mysql_close((MYSQL*)connection);
|
||||
connection = NULL;
|
||||
//switch_to_arena(context->mem);
|
||||
return(false);
|
||||
}
|
||||
|
||||
@@ -37,8 +51,6 @@ bool MySQL::connect(String host, String username, String password)
|
||||
*/
|
||||
//switch_to_arena(context->mem);
|
||||
statement_info = String("connected");
|
||||
if(context)
|
||||
context->resources.mysql_connections.push_back(this);
|
||||
return(true);
|
||||
}
|
||||
|
||||
@@ -192,6 +204,12 @@ DTree MySQL::query(String q)
|
||||
statement_info = "mysql positional ? placeholders are not supported; use named :name placeholders";
|
||||
return(DTree());
|
||||
}
|
||||
if(!connection)
|
||||
{
|
||||
_preload_next_error_code = CR_UNKNOWN_ERROR;
|
||||
statement_info = "mysql connection is not open";
|
||||
return(DTree());
|
||||
}
|
||||
_preload_next_error_code = mysql_query((MYSQL*)connection, q.c_str());
|
||||
DTree result;
|
||||
if(_preload_next_error_code == 0)
|
||||
@@ -237,12 +255,8 @@ static bool mysql_has_unquoted_positional_placeholder(String query)
|
||||
|
||||
DTree MySQL::query(String q, StringMap params)
|
||||
{
|
||||
if(mysql_has_unquoted_positional_placeholder(q))
|
||||
{
|
||||
_preload_next_error_code = CR_UNKNOWN_ERROR;
|
||||
statement_info = "mysql positional ? placeholders are not supported; use named :name placeholders";
|
||||
return(DTree());
|
||||
}
|
||||
// Positional ? placeholders survive named substitution (values are always
|
||||
// quoted by escape()), so the check in query(String) covers this path too.
|
||||
return(query(
|
||||
parse_query_parameters(q, params).c_str()
|
||||
));
|
||||
@@ -347,6 +361,8 @@ String MySQL::error()
|
||||
_preload_next_error_code = 0;
|
||||
return(p);
|
||||
}
|
||||
if(!connection)
|
||||
return("");
|
||||
const char* res = mysql_error((MYSQL*)connection);
|
||||
if(res)
|
||||
{
|
||||
|
||||
@@ -33,6 +33,10 @@ struct MySQL {
|
||||
DTree query(String q, StringMap params);
|
||||
DTree get_pending_result();
|
||||
|
||||
// Unregisters from the request's connection tracking, so stack-allocated
|
||||
// instances cannot leave dangling pointers behind for request cleanup.
|
||||
~MySQL() { disconnect(); }
|
||||
|
||||
};
|
||||
|
||||
MySQL* mysql_connect(String host = "localhost", String username = "root", String password = "")
|
||||
|
||||
@@ -70,15 +70,21 @@ bool SQLite::connect(String path)
|
||||
{
|
||||
disconnect();
|
||||
this->path = path;
|
||||
// Register regardless of outcome: tracking is about the wrapper's
|
||||
// lifetime, not the connection's. disconnect()/~SQLite() unregister.
|
||||
sqlite_register_request_connection(this);
|
||||
s32 rc = sqlite3_open_v2(path.c_str(), (sqlite3**)&connection, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_FULLMUTEX, 0);
|
||||
if(rc != SQLITE_OK)
|
||||
{
|
||||
set_error(rc, "sqlite open failed for " + path);
|
||||
disconnect();
|
||||
if(connection)
|
||||
{
|
||||
sqlite3_close((sqlite3*)connection);
|
||||
connection = 0;
|
||||
}
|
||||
return(false);
|
||||
}
|
||||
sqlite3_busy_timeout((sqlite3*)connection, 5000);
|
||||
sqlite_register_request_connection(this);
|
||||
if(!apply_default_pragmas())
|
||||
return(false);
|
||||
statement_info = "connected";
|
||||
@@ -278,9 +284,11 @@ SQLite* sqlite_connect(String path)
|
||||
}
|
||||
|
||||
SQLite* db = new SQLite();
|
||||
db->worker_cache = true;
|
||||
if(db->connect(path) && db->connection)
|
||||
{
|
||||
db->worker_cache = true;
|
||||
sqlite_worker_connection_cache[path] = db;
|
||||
}
|
||||
else
|
||||
db->request_cleanup_delete = true;
|
||||
return(db);
|
||||
@@ -339,6 +347,10 @@ void cleanup_sqlite_connections()
|
||||
context->resources.sqlite_connections.pop_back();
|
||||
if(db->worker_cache)
|
||||
{
|
||||
// A page that ran BEGIN and faulted must not leak its transaction
|
||||
// (and the WAL write lock) into the next request on this worker.
|
||||
if(db->connection && !sqlite3_get_autocommit((sqlite3*)db->connection))
|
||||
sqlite3_exec((sqlite3*)db->connection, "ROLLBACK", 0, 0, 0);
|
||||
db->affected_rows = 0;
|
||||
db->insert_id = 0;
|
||||
db->error_code = SQLITE_OK;
|
||||
|
||||
@@ -17,6 +17,10 @@ struct SQLite {
|
||||
DTree query(String q);
|
||||
DTree query(String q, const StringMap& params);
|
||||
|
||||
// Unregisters from the request's connection tracking, so stack-allocated
|
||||
// instances cannot leave dangling pointers behind for request cleanup.
|
||||
~SQLite() { disconnect(); }
|
||||
|
||||
private:
|
||||
void set_error(s32 code, String info = "");
|
||||
bool apply_default_pragmas();
|
||||
|
||||
+13
-8
@@ -17,17 +17,12 @@ constexpr f64 FILE_LOCK_WAIT_TIMEOUT_SECONDS = 3.0;
|
||||
|
||||
}
|
||||
|
||||
String capture_backtrace_string(u32 max_frames, u32 skip_frames)
|
||||
String backtrace_frames_string(void* const* frames, size_t size, u32 skip_frames)
|
||||
{
|
||||
if(max_frames == 0)
|
||||
return("");
|
||||
|
||||
std::vector<void*> frames(max_frames);
|
||||
size_t size = backtrace(frames.data(), max_frames);
|
||||
if(size == 0)
|
||||
return("");
|
||||
|
||||
char** symbols = backtrace_symbols(frames.data(), size);
|
||||
char** symbols = backtrace_symbols(frames, size);
|
||||
if(!symbols)
|
||||
return("");
|
||||
|
||||
@@ -38,7 +33,17 @@ String capture_backtrace_string(u32 max_frames, u32 skip_frames)
|
||||
trace += "\n";
|
||||
}
|
||||
free(symbols);
|
||||
return(trace);
|
||||
return(trace);
|
||||
}
|
||||
|
||||
String capture_backtrace_string(u32 max_frames, u32 skip_frames)
|
||||
{
|
||||
if(max_frames == 0)
|
||||
return("");
|
||||
|
||||
std::vector<void*> frames(max_frames);
|
||||
size_t size = backtrace(frames.data(), max_frames);
|
||||
return(backtrace_frames_string(frames.data(), size, skip_frames));
|
||||
}
|
||||
|
||||
String signal_name(int sig)
|
||||
|
||||
@@ -65,6 +65,7 @@ bool ws_send(String message, bool binary = false, String scope = "");
|
||||
bool ws_send_to(String connection_id, String message, bool binary = false);
|
||||
bool ws_close(String connection_id = "");
|
||||
|
||||
String backtrace_frames_string(void* const* frames, size_t size, u32 skip_frames = 0);
|
||||
String capture_backtrace_string(u32 max_frames = 32, u32 skip_frames = 0);
|
||||
String signal_name(int sig);
|
||||
|
||||
|
||||
+3
-8
@@ -366,14 +366,9 @@ DTree request_route_from_raw_path(String raw_path, String default_path)
|
||||
|
||||
void request_populate_context_params(Request& context, String default_path)
|
||||
{
|
||||
DTree route = request_query_route(context, default_path);
|
||||
String script_url = request_script_url(context);
|
||||
context.params["SCRIPT_URL"] = script_url;
|
||||
context.params["BASE_URL"] = request_base_url_from_script_url(script_url);
|
||||
context.params["ROUTE_PATH"] = route["l_path"].to_string();
|
||||
context.params["ROUTE_PAGE"] = route["page"].to_string();
|
||||
context.params["ROUTE_PATH_RAW"] = route["raw_path"].to_string();
|
||||
context.params["ROUTE_VALID"] = route["valid"].to_bool() ? "1" : "0";
|
||||
String raw_path;
|
||||
parse_query(context.params["QUERY_STRING"], &raw_path);
|
||||
request_populate_context_params_from_route(context, raw_path, default_path);
|
||||
}
|
||||
|
||||
void request_populate_context_params_from_route(Request& context, String raw_path, String default_path)
|
||||
|
||||
+11
-4
@@ -18,7 +18,10 @@ static sigjmp_buf request_fault_jmp;
|
||||
static volatile sig_atomic_t request_fault_active = 0;
|
||||
static volatile sig_atomic_t request_fault_signal = 0;
|
||||
static Request* request_fault_request = 0;
|
||||
static String request_fault_trace = "";
|
||||
// Raw frame pointers only: the signal handler must not allocate, so frames are
|
||||
// captured here and symbolized after the siglongjmp.
|
||||
static void* request_fault_frames[64];
|
||||
static volatile sig_atomic_t request_fault_frame_count = 0;
|
||||
static int websocket_exec_fd = -1;
|
||||
static String websocket_exec_read_buffer = "";
|
||||
static std::deque<DTree> websocket_exec_pending_jobs;
|
||||
@@ -105,7 +108,7 @@ void on_request_fault_signal(int sig)
|
||||
request_fault_signal = sig;
|
||||
if(request_fault_active && request_fault_request)
|
||||
{
|
||||
request_fault_trace = capture_backtrace_string(32, 1);
|
||||
request_fault_frame_count = backtrace(request_fault_frames, 64);
|
||||
siglongjmp(request_fault_jmp, 1);
|
||||
}
|
||||
on_segfault(sig);
|
||||
@@ -887,7 +890,7 @@ int handle_complete(FastCGIRequest& request) {
|
||||
request_fault_request = &request;
|
||||
request_fault_active = 1;
|
||||
request_fault_signal = 0;
|
||||
request_fault_trace = "";
|
||||
request_fault_frame_count = 0;
|
||||
install_request_fault_handlers();
|
||||
|
||||
String failure_title = "";
|
||||
@@ -898,7 +901,7 @@ int handle_complete(FastCGIRequest& request) {
|
||||
{
|
||||
failure_title = "fatal signal during request";
|
||||
failure_details = "worker recovered before closing the upstream connection";
|
||||
failure_trace = request_fault_trace;
|
||||
failure_trace = backtrace_frames_string(request_fault_frames, request_fault_frame_count, 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1502,6 +1505,10 @@ void init_base_process()
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
// Warm up libgcc's backtrace state so the first in-handler backtrace()
|
||||
// after a fault does not allocate.
|
||||
backtrace(request_fault_frames, 4);
|
||||
|
||||
init_base_process();
|
||||
ensure_proactive_compiler();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user