fix regression how fish_escape_delay_ms is handled

Fish 2.6.0 introduced a regression that keeps setting
`fish_escape_delay_ms` as a uvar from working. This also fixes a related
problem: callbacks generated from the initial loading of universal vars
were not being acted on.

Fixes #4196
This commit is contained in:
Kurtis Rader 2017-07-14 10:45:31 -07:00
parent 2d5b698f0b
commit 8f548962b7
6 changed files with 64 additions and 53 deletions

View file

@ -26,6 +26,7 @@
- Fix setting `$COLUMNS` and `$LINES` before first prompt is displayed (#4141). - Fix setting `$COLUMNS` and `$LINES` before first prompt is displayed (#4141).
- `read` failures due to too much data should define the var (#4180). - `read` failures due to too much data should define the var (#4180).
- multiple `read` commands in non-interactive scripts were broken in fish 2.6.0 (#4206). - multiple `read` commands in non-interactive scripts were broken in fish 2.6.0 (#4206).
- Fix regression involving universal variables with side-effects at startup such as `set -U fish_escape_delay_ms 10` (#4196).
- Added completions for: - Added completions for:
- `as` (#4130). - `as` (#4130).
- `jest` (#4142). - `jest` (#4142).

View file

@ -588,7 +588,8 @@ static bool variable_is_colon_delimited_var(const wcstring &str) {
/// React to modifying the given variable. /// React to modifying the given variable.
static void react_to_variable_change(const wcstring &key) { static void react_to_variable_change(const wcstring &key) {
// Don't do any of this until `env_init()` has run. We only want to do this in response to // Don't do any of this until `env_init()` has run. We only want to do this in response to
// variables set by the user; e.g., in a script like *config.fish* or interactively. // variables set by the user; e.g., in a script like *config.fish* or interactively or as part
// of loading the universal variables for the first time.
if (!env_initialized) return; if (!env_initialized) return;
if (var_is_locale(key)) { if (var_is_locale(key)) {
@ -753,6 +754,26 @@ void misc_init() {
#endif // OS_IS_MS_WINDOWS #endif // OS_IS_MS_WINDOWS
} }
static void env_universal_callbacks(callback_data_list_t &callbacks) {
for (size_t i = 0; i < callbacks.size(); i++) {
const callback_data_t &data = callbacks.at(i);
universal_callback(data.type, data.key.c_str());
}
}
void env_universal_barrier() {
ASSERT_IS_MAIN_THREAD();
if (!uvars()) return;
callback_data_list_t callbacks;
bool changed = uvars()->sync(callbacks);
if (changed) {
universal_notifier_t::default_notifier().post_notification();
}
env_universal_callbacks(callbacks);
}
void env_init(const struct config_paths_t *paths /* or NULL */) { void env_init(const struct config_paths_t *paths /* or NULL */) {
// These variables can not be altered directly by the user. // These variables can not be altered directly by the user.
const wchar_t *const ro_keys[] = { const wchar_t *const ro_keys[] = {
@ -869,11 +890,6 @@ void env_init(const struct config_paths_t *paths /* or NULL */) {
env_set_termsize(); // initialize the terminal size variables env_set_termsize(); // initialize the terminal size variables
env_set_read_limit(); // initialize the read_byte_limit env_set_read_limit(); // initialize the read_byte_limit
// Set up universal variables. The empty string means to use the deafult path.
assert(s_universal_variables == NULL);
s_universal_variables = new env_universal_t(L"");
s_universal_variables->load();
// Set g_use_posix_spawn. Default to true. // Set g_use_posix_spawn. Default to true.
env_var_t use_posix_spawn = env_get_string(L"fish_use_posix_spawn"); env_var_t use_posix_spawn = env_get_string(L"fish_use_posix_spawn");
g_use_posix_spawn = g_use_posix_spawn =
@ -882,11 +898,23 @@ void env_init(const struct config_paths_t *paths /* or NULL */) {
// Set fish_bind_mode to "default". // Set fish_bind_mode to "default".
env_set(FISH_BIND_MODE_VAR, DEFAULT_BIND_MODE, ENV_GLOBAL); env_set(FISH_BIND_MODE_VAR, DEFAULT_BIND_MODE, ENV_GLOBAL);
// This is somewhat subtle. At this point we consider our environment to be sufficiently
// initialized that we can react to changes to variables. Prior to doing this we expect that the
// code for setting vars that might have side-effects will do whatever
// `react_to_variable_change()` would do for that var.
env_initialized = true;
// Set up universal variables. The empty string means to use the default path.
assert(s_universal_variables == NULL);
s_universal_variables = new env_universal_t(L"");
callback_data_list_t callbacks;
s_universal_variables->load(callbacks);
env_universal_callbacks(callbacks);
// Now that the global scope is fully initialized, add a toplevel local scope. This same local // Now that the global scope is fully initialized, add a toplevel local scope. This same local
// scope will persist throughout the lifetime of the fish process, and it will ensure that `set // scope will persist throughout the lifetime of the fish process, and it will ensure that `set
// -l` commands run at the command-line don't affect the global scope. // -l` commands run at the command-line don't affect the global scope.
env_push(false); env_push(false);
env_initialized = true;
} }
/// Search all visible scopes in order for the specified key. Return the first scope in which it was /// Search all visible scopes in order for the specified key. Return the first scope in which it was
@ -1482,23 +1510,6 @@ env_vars_snapshot_t::env_vars_snapshot_t(const wchar_t *const *keys) {
} }
} }
void env_universal_barrier() {
ASSERT_IS_MAIN_THREAD();
if (uvars()) {
callback_data_list_t changes;
bool changed = uvars()->sync(&changes);
if (changed) {
universal_notifier_t::default_notifier().post_notification();
}
// Post callbacks.
for (size_t i = 0; i < changes.size(); i++) {
const callback_data_t &data = changes.at(i);
universal_callback(data.type, data.key.c_str());
}
}
}
env_vars_snapshot_t::env_vars_snapshot_t() {} env_vars_snapshot_t::env_vars_snapshot_t() {}
// The "current" variables are not a snapshot at all, but instead trampoline to env_get_string, etc. // The "current" variables are not a snapshot at all, but instead trampoline to env_get_string, etc.

View file

@ -346,9 +346,7 @@ wcstring_list_t env_universal_t::get_names(bool show_exported, bool show_unexpor
// Given a variable table, generate callbacks representing the difference between our vars and the // Given a variable table, generate callbacks representing the difference between our vars and the
// new vars. // new vars.
void env_universal_t::generate_callbacks(const var_table_t &new_vars, void env_universal_t::generate_callbacks(const var_table_t &new_vars,
callback_data_list_t *callbacks) const { callback_data_list_t &callbacks) const {
assert(callbacks != NULL);
// Construct callbacks for erased values. // Construct callbacks for erased values.
for (var_table_t::const_iterator iter = this->vars.begin(); iter != this->vars.end(); ++iter) { for (var_table_t::const_iterator iter = this->vars.begin(); iter != this->vars.end(); ++iter) {
const wcstring &key = iter->first; const wcstring &key = iter->first;
@ -360,7 +358,7 @@ void env_universal_t::generate_callbacks(const var_table_t &new_vars,
// If the value is not present in new_vars, it has been erased. // If the value is not present in new_vars, it has been erased.
if (new_vars.find(key) == new_vars.end()) { if (new_vars.find(key) == new_vars.end()) {
callbacks->push_back(callback_data_t(ERASE, key, L"")); callbacks.push_back(callback_data_t(ERASE, key, L""));
} }
} }
@ -379,7 +377,7 @@ void env_universal_t::generate_callbacks(const var_table_t &new_vars,
if (existing == this->vars.end() || existing->second.exportv != new_entry.exportv || if (existing == this->vars.end() || existing->second.exportv != new_entry.exportv ||
existing->second.val != new_entry.val) { existing->second.val != new_entry.val) {
// Value has changed. // Value has changed.
callbacks->push_back( callbacks.push_back(
callback_data_t(new_entry.exportv ? SET_EXPORT : SET, key, new_entry.val)); callback_data_t(new_entry.exportv ? SET_EXPORT : SET, key, new_entry.val));
} }
} }
@ -408,7 +406,7 @@ void env_universal_t::acquire_variables(var_table_t *vars_to_acquire) {
this->vars = std::move(*vars_to_acquire); this->vars = std::move(*vars_to_acquire);
} }
void env_universal_t::load_from_fd(int fd, callback_data_list_t *callbacks) { void env_universal_t::load_from_fd(int fd, callback_data_list_t &callbacks) {
ASSERT_IS_LOCKED(lock); ASSERT_IS_LOCKED(lock);
assert(fd >= 0); assert(fd >= 0);
// Get the dev / inode. // Get the dev / inode.
@ -420,9 +418,7 @@ void env_universal_t::load_from_fd(int fd, callback_data_list_t *callbacks) {
var_table_t new_vars = this->read_message_internal(fd); var_table_t new_vars = this->read_message_internal(fd);
// Announce changes. // Announce changes.
if (callbacks != NULL) { this->generate_callbacks(new_vars, callbacks);
this->generate_callbacks(new_vars, callbacks);
}
// Acquire the new variables. // Acquire the new variables.
this->acquire_variables(&new_vars); this->acquire_variables(&new_vars);
@ -430,7 +426,7 @@ void env_universal_t::load_from_fd(int fd, callback_data_list_t *callbacks) {
} }
} }
bool env_universal_t::load_from_path(const wcstring &path, callback_data_list_t *callbacks) { bool env_universal_t::load_from_path(const wcstring &path, callback_data_list_t &callbacks) {
ASSERT_IS_LOCKED(lock); ASSERT_IS_LOCKED(lock);
// Check to see if the file is unchanged. We do this again in load_from_fd, but this avoids // Check to see if the file is unchanged. We do this again in load_from_fd, but this avoids
@ -507,12 +503,11 @@ bool env_universal_t::move_new_vars_file_into_place(const wcstring &src, const w
return ret == 0; return ret == 0;
} }
bool env_universal_t::load() { bool env_universal_t::load(callback_data_list_t &callbacks) {
callback_data_list_t callbacks;
const wcstring vars_path = const wcstring vars_path =
explicit_vars_path.empty() ? default_vars_path() : explicit_vars_path; explicit_vars_path.empty() ? default_vars_path() : explicit_vars_path;
scoped_lock locker(lock); scoped_lock locker(lock);
bool success = load_from_path(vars_path, &callbacks); bool success = load_from_path(vars_path, callbacks);
if (!success && !tried_renaming && errno == ENOENT) { if (!success && !tried_renaming && errno == ENOENT) {
// We failed to load, because the file was not found. Older fish used the hostname only. Try // We failed to load, because the file was not found. Older fish used the hostname only. Try
// moving the filename based on the hostname into place; if that succeeds try again. // moving the filename based on the hostname into place; if that succeeds try again.
@ -523,10 +518,11 @@ bool env_universal_t::load() {
const wcstring hostname_path = wdirname(vars_path) + L'/' + hostname_id; const wcstring hostname_path = wdirname(vars_path) + L'/' + hostname_id;
if (0 == wrename(hostname_path, vars_path)) { if (0 == wrename(hostname_path, vars_path)) {
// We renamed - try again. // We renamed - try again.
success = this->load(); success = this->load(callbacks);
} }
} }
} }
return success; return success;
} }
@ -645,7 +641,7 @@ bool env_universal_t::open_and_acquire_lock(const wcstring &path, int *out_fd) {
// Returns true if modified variables were written, false if not. (There may still be variable // Returns true if modified variables were written, false if not. (There may still be variable
// changes due to other processes on a false return). // changes due to other processes on a false return).
bool env_universal_t::sync(callback_data_list_t *callbacks) { bool env_universal_t::sync(callback_data_list_t &callbacks) {
debug(5, L"universal log sync"); debug(5, L"universal log sync");
scoped_lock locker(lock); scoped_lock locker(lock);
// Our saving strategy: // Our saving strategy:

View file

@ -41,8 +41,8 @@ class env_universal_t {
mutable pthread_mutex_t lock; mutable pthread_mutex_t lock;
bool tried_renaming; bool tried_renaming;
bool load_from_path(const wcstring &path, callback_data_list_t *callbacks); bool load_from_path(const wcstring &path, callback_data_list_t &callbacks);
void load_from_fd(int fd, callback_data_list_t *callbacks); void load_from_fd(int fd, callback_data_list_t &callbacks);
void set_internal(const wcstring &key, const wcstring &val, bool exportv, bool overwrite); void set_internal(const wcstring &key, const wcstring &val, bool exportv, bool overwrite);
bool remove_internal(const wcstring &name); bool remove_internal(const wcstring &name);
@ -58,7 +58,7 @@ class env_universal_t {
// Given a variable table, generate callbacks representing the difference between our vars and // Given a variable table, generate callbacks representing the difference between our vars and
// the new vars. // the new vars.
void generate_callbacks(const var_table_t &new_vars, callback_data_list_t *callbacks) const; void generate_callbacks(const var_table_t &new_vars, callback_data_list_t &callbacks) const;
// Given a variable table, copy unmodified values into self. May destructively modified // Given a variable table, copy unmodified values into self. May destructively modified
// vars_to_acquire. // vars_to_acquire.
@ -87,11 +87,11 @@ class env_universal_t {
wcstring_list_t get_names(bool show_exported, bool show_unexported) const; wcstring_list_t get_names(bool show_exported, bool show_unexported) const;
/// Loads variables at the correct path. /// Loads variables at the correct path.
bool load(); bool load(callback_data_list_t &callbacks);
/// Reads and writes variables at the correct path. Returns true if modified variables were /// Reads and writes variables at the correct path. Returns true if modified variables were
/// written. /// written.
bool sync(callback_data_list_t *callbacks); bool sync(callback_data_list_t &callbacks);
}; };
/// The "universal notifier" is an object responsible for broadcasting and receiving universal /// The "universal notifier" is an object responsible for broadcasting and receiving universal

View file

@ -2621,12 +2621,13 @@ static void test_input() {
#define UVARS_TEST_PATH L"test/fish_uvars_test/varsfile.txt" #define UVARS_TEST_PATH L"test/fish_uvars_test/varsfile.txt"
static int test_universal_helper(int x) { static int test_universal_helper(int x) {
callback_data_list_t callbacks;
env_universal_t uvars(UVARS_TEST_PATH); env_universal_t uvars(UVARS_TEST_PATH);
for (int j = 0; j < UVARS_PER_THREAD; j++) { for (int j = 0; j < UVARS_PER_THREAD; j++) {
const wcstring key = format_string(L"key_%d_%d", x, j); const wcstring key = format_string(L"key_%d_%d", x, j);
const wcstring val = format_string(L"val_%d_%d", x, j); const wcstring val = format_string(L"val_%d_%d", x, j);
uvars.set(key, val, false); uvars.set(key, val, false);
bool synced = uvars.sync(NULL); bool synced = uvars.sync(callbacks);
if (!synced) { if (!synced) {
err(L"Failed to sync universal variables after modification"); err(L"Failed to sync universal variables after modification");
} }
@ -2634,7 +2635,7 @@ static int test_universal_helper(int x) {
// Last step is to delete the first key. // Last step is to delete the first key.
uvars.remove(format_string(L"key_%d_%d", x, 0)); uvars.remove(format_string(L"key_%d_%d", x, 0));
bool synced = uvars.sync(NULL); bool synced = uvars.sync(callbacks);
if (!synced) { if (!synced) {
err(L"Failed to sync universal variables after deletion"); err(L"Failed to sync universal variables after deletion");
} }
@ -2652,7 +2653,8 @@ static void test_universal() {
iothread_drain_all(); iothread_drain_all();
env_universal_t uvars(UVARS_TEST_PATH); env_universal_t uvars(UVARS_TEST_PATH);
bool loaded = uvars.load(); callback_data_list_t callbacks;
bool loaded = uvars.load(callbacks);
if (!loaded) { if (!loaded) {
err(L"Failed to load universal variables"); err(L"Failed to load universal variables");
} }
@ -2685,6 +2687,8 @@ static bool callback_data_less_than(const callback_data_t &a, const callback_dat
static void test_universal_callbacks() { static void test_universal_callbacks() {
say(L"Testing universal callbacks"); say(L"Testing universal callbacks");
callback_data_list_t callbacks;
if (system("mkdir -p test/fish_uvars_test/")) err(L"mkdir failed"); if (system("mkdir -p test/fish_uvars_test/")) err(L"mkdir failed");
env_universal_t uvars1(UVARS_TEST_PATH); env_universal_t uvars1(UVARS_TEST_PATH);
env_universal_t uvars2(UVARS_TEST_PATH); env_universal_t uvars2(UVARS_TEST_PATH);
@ -2698,23 +2702,23 @@ static void test_universal_callbacks() {
uvars1.set(L"kappa", L"1", false); uvars1.set(L"kappa", L"1", false);
uvars1.set(L"omicron", L"1", false); uvars1.set(L"omicron", L"1", false);
uvars1.sync(NULL); uvars1.sync(callbacks);
uvars2.sync(NULL); uvars2.sync(callbacks);
// Change uvars1. // Change uvars1.
uvars1.set(L"alpha", L"2", false); // changes value uvars1.set(L"alpha", L"2", false); // changes value
uvars1.set(L"beta", L"1", true); // changes export uvars1.set(L"beta", L"1", true); // changes export
uvars1.remove(L"delta"); // erases value uvars1.remove(L"delta"); // erases value
uvars1.set(L"epsilon", L"1", false); // changes nothing uvars1.set(L"epsilon", L"1", false); // changes nothing
uvars1.sync(NULL); uvars1.sync(callbacks);
// Change uvars2. It should treat its value as correct and ignore changes from uvars1. // Change uvars2. It should treat its value as correct and ignore changes from uvars1.
uvars2.set(L"lambda", L"1", false); // same value uvars2.set(L"lambda", L"1", false); // same value
uvars2.set(L"kappa", L"2", false); // different value uvars2.set(L"kappa", L"2", false); // different value
// Now see what uvars2 sees. // Now see what uvars2 sees.
callback_data_list_t callbacks; callbacks.clear();
uvars2.sync(&callbacks); uvars2.sync(callbacks);
// Sort them to get them in a predictable order. // Sort them to get them in a predictable order.
std::sort(callbacks.begin(), callbacks.end(), callback_data_less_than); std::sort(callbacks.begin(), callbacks.end(), callback_data_less_than);

View file

@ -60,7 +60,6 @@ static int (*interrupt_handler)();
void input_common_init(int (*ih)()) { void input_common_init(int (*ih)()) {
interrupt_handler = ih; interrupt_handler = ih;
update_wait_on_escape_ms();
} }
void input_common_destroy() {} void input_common_destroy() {}