libexperiments: properly refresh experiment states
An experiment is enabled if the file /config/experiment/<exp>.active
exists. The old code assumed that Catawampus would create/delete this
file whenever the state of an experiment changed (as pushed by ACS).
It turns out that it does not do that and instead just creates a new
file, either <exp>.requested or <exp>.unrequested, and then expects
the user process to check for that and create/delete the <exp>.active
accordingly. The new code does this now.
I also dumped all my understanding of the experiments framework into
go/gfiber-experiments-for-the-rest-of-us. Please read there for more
details.
Change-Id: Ibdd4fcce447905ce6f63694357e9f335d630872f
diff --git a/libexperiments/experiments.cc b/libexperiments/experiments.cc
index 89fec64..daf3e20 100644
--- a/libexperiments/experiments.cc
+++ b/libexperiments/experiments.cc
@@ -70,9 +70,6 @@
if (!names_to_register.empty()) {
if (!Register_Locked(names_to_register))
return false;
-
- // initial read of registered experiments states
- Refresh();
}
return true;
@@ -104,6 +101,13 @@
registered_experiments_.insert(name);
log("experiments:Registered '%s'", name.c_str());
+
+ // check if experiment is active
+ std::string file_active = config_dir_ + "/" + name + ".active";
+ if (file_exists(file_active.c_str())) {
+ enabled_experiments_.insert(name);
+ log("experiments:'%s' is now enabled", name.c_str());
+ }
}
return true;
}
@@ -138,15 +142,29 @@
return;
}
- std::string file_path = config_dir_ + "/" + name + ".active";
- bool was_enabled = IsInEnabledList(name);
- bool is_enabled = file_exists(file_path.c_str());
- if (is_enabled && !was_enabled) {
- log("experiments:'%s' is now enabled", name.c_str());
- enabled_experiments_.insert(name);
- } else if (!is_enabled && was_enabled) {
- log("experiments:'%s' is now disabled", name.c_str());
- enabled_experiments_.erase(name);
+ std::string experiments_path = config_dir_ + "/" + name;
+
+ if (IsInEnabledList(name)) {
+ // currently enabled, check if it was set to ".unrequested"
+ std::string file_unrequested = experiments_path + ".unrequested";
+ if (file_exists(file_unrequested.c_str())) {
+ // deactivate experiment
+ std::string file_active = experiments_path + ".active";
+ rm_file(file_active.c_str());
+ rm_file(file_unrequested.c_str());
+ enabled_experiments_.erase(name);
+ log("experiments:'%s' is now disabled", name.c_str());
+ }
+ } else {
+ // currently not enabled, check if it is requested
+ std::string file_requested = experiments_path + ".requested";
+ if (file_exists(file_requested.c_str())) {
+ // activate experiment
+ std::string file_active = experiments_path + ".active";
+ mv_file(file_requested.c_str(), file_active.c_str());
+ enabled_experiments_.insert(name);
+ log("experiments:'%s' is now enabled", name.c_str());
+ }
}
}
diff --git a/libexperiments/experiments_test.cc b/libexperiments/experiments_test.cc
index 5f0178d..659b430 100644
--- a/libexperiments/experiments_test.cc
+++ b/libexperiments/experiments_test.cc
@@ -3,7 +3,6 @@
#include "experiments.h"
#include "experiments_c_api_test.h"
-#include <fcntl.h>
#include <linux/limits.h>
#include <sys/types.h>
#include <sys/stat.h>
@@ -35,68 +34,20 @@
ASSERT_EQ(0, system(cmd.c_str()));
}
- bool CreateFile(const std::string &name) {
- int fd = open(name.c_str(), O_CREAT | O_TRUNC,
- S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
- if (fd < 0) {
- log_perror(errno, "Cannot create file '%s':", name.c_str());
- return false;
- } else {
- close(fd);
- }
- return true;
+ bool SetRequested(const std::string &exp_name) {
+ remove((exp_name + ".unrequested").c_str());
+ return touch_file((exp_name + ".requested").c_str());
}
- bool RenameFile(const std::string &from_name, const std::string &to_name) {
- if (rename(from_name.c_str(), to_name.c_str()) < 0) {
- log_perror(errno, "Cannot rename file '%s' to '%s':", from_name.c_str(),
- to_name.c_str());
- return false;
- }
- return true;
+ bool SetUnrequested(const std::string &exp_name) {
+ remove((exp_name + ".requested").c_str());
+ return touch_file((exp_name + ".unrequested").c_str());
}
- bool DeleteFile(const std::string &name) {
- if (remove(name.c_str()) < 0) {
- log_perror(errno, "Cannot delete file '%s':", name.c_str());
- return false;
- }
- return true;
- }
-
- bool SwitchFromTo(Experiments *e, const std::string &name,
- const std::string &from_ext, const std::string &to_ext) {
- std::string from_file = name + from_ext;
- std::string to_file = name + to_ext;
- if (file_exists(from_file.c_str())) {
- return RenameFile(from_file, to_file);
- } else {
- return CreateFile(to_file);
- }
- }
-
- bool SetActive(Experiments *e, const std::string &name) {
- return SwitchFromTo(e, name, ".inactive", ".active");
- }
-
- bool SetInactive(Experiments *e, const std::string &name) {
- return SwitchFromTo(e, name, ".active", ".inactive");
- }
-
- bool Remove(Experiments *e, const std::string &name) {
- std::string active_file = name + ".active";
- if (file_exists(active_file.c_str())) {
- if (!DeleteFile(active_file)) {
- return false;
- }
- }
- std::string inactive_file = name + ".inactive";
- if (file_exists(inactive_file.c_str())) {
- if (!DeleteFile(inactive_file)) {
- return false;
- }
- }
- return true;
+ void Remove(const std::string &exp_name) {
+ remove((exp_name + ".unrequested").c_str());
+ remove((exp_name + ".requested").c_str());
+ remove((exp_name + ".active").c_str());
}
static char root_path_[PATH_MAX];
@@ -163,17 +114,20 @@
EXPECT_FALSE(e.IsEnabled("exp1"));
EXPECT_EQ(1, e.GetNumOfRegisteredExperiments());
- EXPECT_TRUE(SetActive(&e, "exp1"));
+ EXPECT_TRUE(SetRequested("exp1"));
EXPECT_TRUE(e.IsEnabled("exp1"));
- EXPECT_TRUE(SetInactive(&e, "exp1"));
+ EXPECT_TRUE(SetUnrequested("exp1"));
EXPECT_FALSE(e.IsEnabled("exp1"));
- EXPECT_TRUE(SetActive(&e, "exp1"));
+ EXPECT_TRUE(SetRequested("exp1"));
EXPECT_TRUE(e.IsEnabled("exp1"));
- EXPECT_TRUE(Remove(&e, "exp1"));
+ EXPECT_TRUE(SetUnrequested("exp1"));
EXPECT_FALSE(e.IsEnabled("exp1"));
+
+ // clean up
+ Remove("exp1");
}
TEST_F(ExperimentsTest, Multiple) {
@@ -186,47 +140,52 @@
EXPECT_FALSE(e.IsEnabled("exp3"));
// activate exp1 - AII
- EXPECT_TRUE(SetActive(&e, "exp1"));
+ EXPECT_TRUE(SetRequested("exp1"));
EXPECT_TRUE(e.IsEnabled("exp1"));
EXPECT_FALSE(e.IsEnabled("exp2"));
EXPECT_FALSE(e.IsEnabled("exp3"));
// activate exp2 - AAI
- EXPECT_TRUE(SetActive(&e, "exp2"));
+ EXPECT_TRUE(SetRequested("exp2"));
EXPECT_TRUE(e.IsEnabled("exp1"));
EXPECT_TRUE(e.IsEnabled("exp2"));
EXPECT_FALSE(e.IsEnabled("exp3"));
// active exp3 - AAA
- EXPECT_TRUE(SetActive(&e, "exp3"));
+ EXPECT_TRUE(SetRequested("exp3"));
EXPECT_TRUE(e.IsEnabled("exp1"));
EXPECT_TRUE(e.IsEnabled("exp2"));
EXPECT_TRUE(e.IsEnabled("exp3"));
// inactivate exp2 - AIA
- EXPECT_TRUE(SetInactive(&e, "exp2"));
+ EXPECT_TRUE(SetUnrequested("exp2"));
EXPECT_TRUE(e.IsEnabled("exp1"));
EXPECT_FALSE(e.IsEnabled("exp2"));
EXPECT_TRUE(e.IsEnabled("exp3"));
- // remove exp1 file - IIA
- EXPECT_TRUE(Remove(&e, "exp1"));
+ // inactivate exp1 file - IIA
+ EXPECT_TRUE(SetUnrequested("exp1"));
EXPECT_FALSE(e.IsEnabled("exp1"));
EXPECT_FALSE(e.IsEnabled("exp2"));
EXPECT_TRUE(e.IsEnabled("exp3"));
// re-activate exp2 - IAA
- EXPECT_TRUE(SetActive(&e, "exp2"));
+ EXPECT_TRUE(SetRequested("exp2"));
EXPECT_FALSE(e.IsEnabled("exp1"));
EXPECT_TRUE(e.IsEnabled("exp2"));
EXPECT_TRUE(e.IsEnabled("exp3"));
// inactivate exp1 (re-create file) - IAA
- EXPECT_TRUE(SetInactive(&e, "exp1"));
+ EXPECT_TRUE(SetUnrequested("exp1"));
EXPECT_FALSE(e.IsEnabled("exp1"));
EXPECT_TRUE(e.IsEnabled("exp2"));
EXPECT_TRUE(e.IsEnabled("exp3"));
- // remove all - III
- EXPECT_TRUE(Remove(&e, "exp1"));
- EXPECT_TRUE(Remove(&e, "exp2"));
- EXPECT_TRUE(Remove(&e, "exp3"));
+ // inactivate all - III
+ EXPECT_TRUE(SetUnrequested("exp1"));
+ EXPECT_TRUE(SetUnrequested("exp2"));
+ EXPECT_TRUE(SetUnrequested("exp3"));
EXPECT_FALSE(e.IsEnabled("exp1"));
EXPECT_FALSE(e.IsEnabled("exp2"));
EXPECT_FALSE(e.IsEnabled("exp3"));
+
+ // clean up
+ Remove("exp1");
+ Remove("exp2");
+ Remove("exp3");
}
TEST_F(ExperimentsTest, TimeBetweenRefresh) {
@@ -238,7 +197,7 @@
&DummyExperimentsRegisterFunc, {"exp1"}));
EXPECT_EQ(1, e.GetNumOfRegisteredExperiments());
EXPECT_FALSE(e.IsEnabled("exp1"));
- EXPECT_TRUE(SetActive(&e, "exp1"));
+ EXPECT_TRUE(SetRequested("exp1"));
// measure time until we see "exp1" active
uint64_t duration = us_elapse(start_time);
@@ -251,7 +210,7 @@
EXPECT_LT(duration, kTimeout) << "time:" << duration;
// clean up
- EXPECT_TRUE(Remove(&e, "exp1"));
+ Remove("exp1");
}
TEST_F(ExperimentsTest, C_API_Test) {
@@ -260,9 +219,9 @@
EXPECT_FALSE(test_experiments_register("exp1"));
EXPECT_FALSE(test_experiments_is_registered("exp1"));
EXPECT_FALSE(test_experiments_is_enabled("exp1"));
- EXPECT_TRUE(SetActive(experiments, "exp1"));
+ EXPECT_TRUE(SetRequested("exp1"));
EXPECT_FALSE(test_experiments_is_enabled("exp1"));
- EXPECT_TRUE(Remove(experiments, "exp1"));
+ EXPECT_TRUE(SetUnrequested("exp1"));
// initialize
EXPECT_TRUE(test_experiments_initialize(test_folder_path_));
@@ -274,13 +233,13 @@
EXPECT_EQ(1, experiments_get_num_of_registered_experiments());
EXPECT_FALSE(test_experiments_is_enabled("exp1"));
- EXPECT_TRUE(SetActive(experiments, "exp1"));
+ EXPECT_TRUE(SetRequested("exp1"));
EXPECT_TRUE(test_experiments_is_enabled("exp1"));
EXPECT_FALSE(test_experiments_is_enabled("exp2"));
- EXPECT_TRUE(SetInactive(experiments, "exp1"));
+ EXPECT_TRUE(SetUnrequested("exp1"));
EXPECT_FALSE(test_experiments_is_enabled("exp1"));
// clean up
- EXPECT_TRUE(Remove(experiments, "exp1"));
+ EXPECT_TRUE(SetUnrequested("exp1"));
}
diff --git a/libexperiments/utils.h b/libexperiments/utils.h
index 3c1adb4..2bb8ddf 100644
--- a/libexperiments/utils.h
+++ b/libexperiments/utils.h
@@ -1,6 +1,7 @@
#ifndef _LIBEXPERIMENTS_UTILS_H_
#define _LIBEXPERIMENTS_UTILS_H_
+#include <fcntl.h>
#include <stdint.h>
#include <sys/time.h>
#include <sys/types.h>
@@ -53,6 +54,42 @@
return S_ISDIR(dir_stat.st_mode);
}
+static inline bool touch_file(const char *name) {
+ if (!name)
+ return false;
+
+ int fd = open(name, O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
+ if (fd < 0) {
+ log_perror(errno, "Cannot create file '%s':", name);
+ return false;
+ }
+ close(fd);
+ return true;
+}
+
+static inline bool rm_file(const char *name) {
+ if (!name)
+ return false;
+
+ if (remove(name) < 0) {
+ log_perror(errno, "Cannot rm file '%s':", name);
+ return false;
+ }
+ return true;
+}
+
+static inline bool mv_file(const char *from_name, const char *to_name) {
+ if (!from_name || !to_name)
+ return false;
+
+ // Note this uses rename() and hence only works on the same filesystem.
+ if (rename(from_name, to_name) < 0) {
+ log_perror(errno, "Cannot mv file '%s' to '%s':", from_name, to_name);
+ return false;
+ }
+ return true;
+}
+
// This function runs the command cmd (but not in a shell), providing the
// return code, stdout, and stderr. It also allows to specify the stdin,
// and a command timeout value (timeout_usec, use <0 to block indefinitely).