platform_bionic/tests/system_properties_test.cpp
Daniel Colascione 0cf90556de Make system property reads wait-free
Right now, when we read a system property, we first (assuming we've
already looked up the property's prop_info) read the property's serial
number; if we find that the low bit (the dirty bit) in the serial
number is set, we futex-wait for that serial number to become
non-dirty. By doing so, we spare readers from seeing partially-updated
property values if they race with the property service's non-atomic
memcpy to the property value slot. (The futex-wait here isn't
essential to the algorithm: spinning while dirty would suffice,
although it'd be somewhat less efficient.)

The problem with this approach is that readers can wait on the
property service process, potentially causing delays due to scheduling
variance. Property reads are not guaranteed to complete in finite time
right now.

This change makes property reads wait-free and ensures that they
complete in finite time in all cases. In the new approach, we prevent
value tearing by backing up each property we're about to modify and
directing readers to the backup copy if they try to read a property
with the dirty bit set.

(The wait freedom is limited to the case of readers racing against
*one* property update. A writer can still delay readers by rapidly
updating a property --- but after this change, readers can't hang due
to PID 1 scheduling delays.)

I considered adding explicit atomic access to short property values,
but between binary compatibility with the existing property database
and the need to carefully handle transitions of property values
between "short" (compatible with atomics) and "long" (incompatible
with atomics) length domains, I figured the complexity wasn't worth it
and that making property reads wait-free would be adequate.

Test: boots
Bug: 143561649
Change-Id: Ifd3108aedba5a4b157b66af6ca0a4ed084bd5982
2019-11-11 11:17:00 -08:00

528 lines
17 KiB
C++

/*
* Copyright (C) 2013 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <gtest/gtest.h>
#include "BionicDeathTest.h"
#include <errno.h>
#include <sys/wait.h>
#include <unistd.h>
#include <string>
#include <thread>
#include <android-base/file.h>
using namespace std::literals;
#if defined(__BIONIC__)
#define _REALLY_INCLUDE_SYS__SYSTEM_PROPERTIES_H_
#include <sys/_system_properties.h>
#include <system_properties/system_properties.h>
class SystemPropertiesTest : public SystemProperties {
public:
SystemPropertiesTest() : SystemProperties(false) {
valid_ = AreaInit(dir_.path, nullptr);
}
~SystemPropertiesTest() {
if (valid_) {
contexts_->FreeAndUnmap();
}
}
bool valid() const {
return valid_;
}
private:
TemporaryDir dir_;
bool valid_;
};
static void foreach_test_callback(const prop_info *pi, void* cookie) {
size_t *count = static_cast<size_t *>(cookie);
ASSERT_TRUE(pi != nullptr);
(*count)++;
}
static void hierarchical_test_callback(const prop_info *pi, void *cookie) {
bool (*ok)[8][8] = static_cast<bool (*)[8][8]>(cookie);
char name[PROP_NAME_MAX];
char value[PROP_VALUE_MAX];
__system_property_read(pi, name, value);
int name_i, name_j, name_k;
int value_i, value_j, value_k;
ASSERT_EQ(3, sscanf(name, "property_%d.%d.%d", &name_i, &name_j, &name_k));
ASSERT_EQ(3, sscanf(value, "value_%d.%d.%d", &value_i, &value_j, &value_k));
ASSERT_EQ(name_i, value_i);
ASSERT_GE(name_i, 0);
ASSERT_LT(name_i, 8);
ASSERT_EQ(name_j, value_j);
ASSERT_GE(name_j, 0);
ASSERT_LT(name_j, 8);
ASSERT_EQ(name_k, value_k);
ASSERT_GE(name_k, 0);
ASSERT_LT(name_k, 8);
ok[name_i][name_j][name_k] = true;
}
#endif // __BIONIC__
TEST(properties, __system_property_add) {
#if defined(__BIONIC__)
SystemPropertiesTest system_properties;
ASSERT_TRUE(system_properties.valid());
ASSERT_EQ(0, system_properties.Add("property", 8, "value1", 6));
ASSERT_EQ(0, system_properties.Add("other_property", 14, "value2", 6));
ASSERT_EQ(0, system_properties.Add("property_other", 14, "value3", 6));
// check that there is no limit on property name length
char name[PROP_NAME_MAX + 11];
name[0] = 'p';
for (size_t i = 1; i < sizeof(name); i++) {
name[i] = 'x';
}
name[sizeof(name)-1] = '\0';
ASSERT_EQ(0, system_properties.Add(name, strlen(name), "value", 5));
char propvalue[PROP_VALUE_MAX];
ASSERT_EQ(6, system_properties.Get("property", propvalue));
ASSERT_STREQ(propvalue, "value1");
ASSERT_EQ(6, system_properties.Get("other_property", propvalue));
ASSERT_STREQ(propvalue, "value2");
ASSERT_EQ(6, system_properties.Get("property_other", propvalue));
ASSERT_STREQ(propvalue, "value3");
ASSERT_EQ(5, system_properties.Get(name, propvalue));
ASSERT_STREQ(propvalue, "value");
#else // __BIONIC__
GTEST_SKIP() << "bionic-only test";
#endif // __BIONIC__
}
TEST(properties, __system_property_update) {
#if defined(__BIONIC__)
SystemPropertiesTest system_properties;
ASSERT_TRUE(system_properties.valid());
ASSERT_EQ(0, system_properties.Add("property", 8, "oldvalue1", 9));
ASSERT_EQ(0, system_properties.Add("other_property", 14, "value2", 6));
ASSERT_EQ(0, system_properties.Add("property_other", 14, "value3", 6));
const prop_info* pi = system_properties.Find("property");
ASSERT_TRUE(pi != nullptr);
system_properties.Update(const_cast<prop_info*>(pi), "value4", 6);
pi = system_properties.Find("other_property");
ASSERT_TRUE(pi != nullptr);
system_properties.Update(const_cast<prop_info*>(pi), "newvalue5", 9);
pi = system_properties.Find("property_other");
ASSERT_TRUE(pi != nullptr);
system_properties.Update(const_cast<prop_info*>(pi), "value6", 6);
char propvalue[PROP_VALUE_MAX];
ASSERT_EQ(6, system_properties.Get("property", propvalue));
ASSERT_STREQ(propvalue, "value4");
ASSERT_EQ(9, system_properties.Get("other_property", propvalue));
ASSERT_STREQ(propvalue, "newvalue5");
ASSERT_EQ(6, system_properties.Get("property_other", propvalue));
ASSERT_STREQ(propvalue, "value6");
#else // __BIONIC__
GTEST_SKIP() << "bionic-only test";
#endif // __BIONIC__
}
TEST(properties, fill) {
#if defined(__BIONIC__)
SystemPropertiesTest system_properties;
ASSERT_TRUE(system_properties.valid());
char prop_name[PROP_NAME_MAX];
char prop_value[PROP_VALUE_MAX];
char prop_value_ret[PROP_VALUE_MAX];
int count = 0;
int ret;
while (true) {
ret = snprintf(prop_name, PROP_NAME_MAX - 1, "property_%d", count);
memset(prop_name + ret, 'a', PROP_NAME_MAX - 1 - ret);
ret = snprintf(prop_value, PROP_VALUE_MAX - 1, "value_%d", count);
memset(prop_value + ret, 'b', PROP_VALUE_MAX - 1 - ret);
prop_name[PROP_NAME_MAX - 1] = 0;
prop_value[PROP_VALUE_MAX - 1] = 0;
ret = system_properties.Add(prop_name, PROP_NAME_MAX - 1, prop_value, PROP_VALUE_MAX - 1);
if (ret < 0)
break;
count++;
}
// For historical reasons at least 247 properties must be supported
ASSERT_GE(count, 247);
for (int i = 0; i < count; i++) {
ret = snprintf(prop_name, PROP_NAME_MAX - 1, "property_%d", i);
memset(prop_name + ret, 'a', PROP_NAME_MAX - 1 - ret);
ret = snprintf(prop_value, PROP_VALUE_MAX - 1, "value_%d", i);
memset(prop_value + ret, 'b', PROP_VALUE_MAX - 1 - ret);
prop_name[PROP_NAME_MAX - 1] = 0;
prop_value[PROP_VALUE_MAX - 1] = 0;
memset(prop_value_ret, '\0', PROP_VALUE_MAX);
ASSERT_EQ(PROP_VALUE_MAX - 1, system_properties.Get(prop_name, prop_value_ret));
ASSERT_EQ(0, memcmp(prop_value, prop_value_ret, PROP_VALUE_MAX));
}
#else // __BIONIC__
GTEST_SKIP() << "bionic-only test";
#endif // __BIONIC__
}
TEST(properties, __system_property_foreach) {
#if defined(__BIONIC__)
SystemPropertiesTest system_properties;
ASSERT_TRUE(system_properties.valid());
ASSERT_EQ(0, system_properties.Add("property", 8, "value1", 6));
ASSERT_EQ(0, system_properties.Add("other_property", 14, "value2", 6));
ASSERT_EQ(0, system_properties.Add("property_other", 14, "value3", 6));
size_t count = 0;
ASSERT_EQ(0, system_properties.Foreach(foreach_test_callback, &count));
ASSERT_EQ(3U, count);
#else // __BIONIC__
GTEST_SKIP() << "bionic-only test";
#endif // __BIONIC__
}
TEST(properties, __system_property_find_nth) {
#if defined(__BIONIC__)
SystemPropertiesTest system_properties;
ASSERT_TRUE(system_properties.valid());
ASSERT_EQ(0, system_properties.Add("property", 8, "value1", 6));
ASSERT_EQ(0, system_properties.Add("other_property", 14, "value2", 6));
ASSERT_EQ(0, system_properties.Add("property_other", 14, "value3", 6));
char name[PROP_NAME_MAX];
char value[PROP_VALUE_MAX];
EXPECT_EQ(6, system_properties.Read(system_properties.FindNth(0), name, value));
EXPECT_STREQ("property", name);
EXPECT_STREQ("value1", value);
EXPECT_EQ(6, system_properties.Read(system_properties.FindNth(1), name, value));
EXPECT_STREQ("other_property", name);
EXPECT_STREQ("value2", value);
EXPECT_EQ(6, system_properties.Read(system_properties.FindNth(2), name, value));
EXPECT_STREQ("property_other", name);
EXPECT_STREQ("value3", value);
for (unsigned i = 3; i < 1024; ++i) {
ASSERT_TRUE(system_properties.FindNth(i) == nullptr);
}
#else // __BIONIC__
GTEST_SKIP() << "bionic-only test";
#endif // __BIONIC__
}
TEST(properties, fill_hierarchical) {
#if defined(__BIONIC__)
SystemPropertiesTest system_properties;
ASSERT_TRUE(system_properties.valid());
char prop_name[PROP_NAME_MAX];
char prop_value[PROP_VALUE_MAX];
char prop_value_ret[PROP_VALUE_MAX];
int ret;
for (int i = 0; i < 8; i++) {
for (int j = 0; j < 8; j++) {
for (int k = 0; k < 8; k++) {
ret = snprintf(prop_name, PROP_NAME_MAX - 1, "property_%d.%d.%d", i, j, k);
memset(prop_name + ret, 'a', PROP_NAME_MAX - 1 - ret);
ret = snprintf(prop_value, PROP_VALUE_MAX - 1, "value_%d.%d.%d", i, j, k);
memset(prop_value + ret, 'b', PROP_VALUE_MAX - 1 - ret);
prop_name[PROP_NAME_MAX - 1] = 0;
prop_value[PROP_VALUE_MAX - 1] = 0;
ASSERT_EQ(0, system_properties.Add(
prop_name, PROP_NAME_MAX - 1, prop_value, PROP_VALUE_MAX - 1));
}
}
}
for (int i = 0; i < 8; i++) {
for (int j = 0; j < 8; j++) {
for (int k = 0; k < 8; k++) {
ret = snprintf(prop_name, PROP_NAME_MAX - 1, "property_%d.%d.%d", i, j, k);
memset(prop_name + ret, 'a', PROP_NAME_MAX - 1 - ret);
ret = snprintf(prop_value, PROP_VALUE_MAX - 1, "value_%d.%d.%d", i, j, k);
memset(prop_value + ret, 'b', PROP_VALUE_MAX - 1 - ret);
prop_name[PROP_NAME_MAX - 1] = 0;
prop_value[PROP_VALUE_MAX - 1] = 0;
memset(prop_value_ret, '\0', PROP_VALUE_MAX);
ASSERT_EQ(PROP_VALUE_MAX - 1, system_properties.Get(prop_name, prop_value_ret));
ASSERT_EQ(0, memcmp(prop_value, prop_value_ret, PROP_VALUE_MAX));
}
}
}
bool ok[8][8][8];
memset(ok, 0, sizeof(ok));
system_properties.Foreach(hierarchical_test_callback, ok);
for (int i = 0; i < 8; i++) {
for (int j = 0; j < 8; j++) {
for (int k = 0; k < 8; k++) {
ASSERT_TRUE(ok[i][j][k]);
}
}
}
#else // __BIONIC__
GTEST_SKIP() << "bionic-only test";
#endif // __BIONIC__
}
TEST(properties, errors) {
#if defined(__BIONIC__)
SystemPropertiesTest system_properties;
ASSERT_TRUE(system_properties.valid());
char prop_value[PROP_NAME_MAX];
ASSERT_EQ(0, system_properties.Add("property", 8, "value1", 6));
ASSERT_EQ(0, system_properties.Add("other_property", 14, "value2", 6));
ASSERT_EQ(0, system_properties.Add("property_other", 14, "value3", 6));
ASSERT_EQ(0, system_properties.Find("property1"));
ASSERT_EQ(0, system_properties.Get("property1", prop_value));
ASSERT_EQ(-1, system_properties.Add("name", 4, "value", PROP_VALUE_MAX));
ASSERT_EQ(-1, system_properties.Update(NULL, "value", PROP_VALUE_MAX));
#else // __BIONIC__
GTEST_SKIP() << "bionic-only test";
#endif // __BIONIC__
}
TEST(properties, __system_property_serial) {
#if defined(__BIONIC__)
SystemPropertiesTest system_properties;
ASSERT_TRUE(system_properties.valid());
ASSERT_EQ(0, system_properties.Add("property", 8, "value1", 6));
const prop_info* pi = system_properties.Find("property");
ASSERT_TRUE(pi != nullptr);
unsigned serial = __system_property_serial(pi);
ASSERT_EQ(0, system_properties.Update(const_cast<prop_info*>(pi), "value2", 6));
ASSERT_NE(serial, __system_property_serial(pi));
#else // __BIONIC__
GTEST_SKIP() << "bionic-only test";
#endif // __BIONIC__
}
TEST(properties, __system_property_wait_any) {
#if defined(__BIONIC__)
SystemPropertiesTest system_properties;
ASSERT_TRUE(system_properties.valid());
ASSERT_EQ(0, system_properties.Add("property", 8, "value1", 6));
unsigned serial = system_properties.WaitAny(0);
prop_info* pi = const_cast<prop_info*>(system_properties.Find("property"));
ASSERT_TRUE(pi != nullptr);
system_properties.Update(pi, "value2", 6);
serial = system_properties.WaitAny(serial);
int flag = 0;
std::thread thread([&system_properties, &flag]() {
prop_info* pi = const_cast<prop_info*>(system_properties.Find("property"));
usleep(100000);
flag = 1;
system_properties.Update(pi, "value3", 6);
});
ASSERT_EQ(flag, 0);
serial = system_properties.WaitAny(serial);
ASSERT_EQ(flag, 1);
thread.join();
#else // __BIONIC__
GTEST_SKIP() << "bionic-only test";
#endif // __BIONIC__
}
TEST(properties, __system_property_wait) {
#if defined(__BIONIC__)
SystemPropertiesTest system_properties;
ASSERT_TRUE(system_properties.valid());
ASSERT_EQ(0, system_properties.Add("property", 8, "value1", 6));
prop_info* pi = const_cast<prop_info*>(system_properties.Find("property"));
ASSERT_TRUE(pi != nullptr);
unsigned serial = __system_property_serial(pi);
std::thread thread([&system_properties]() {
prop_info* pi = const_cast<prop_info*>(system_properties.Find("property"));
ASSERT_TRUE(pi != nullptr);
system_properties.Update(pi, "value2", 6);
});
uint32_t new_serial;
system_properties.Wait(pi, serial, &new_serial, nullptr);
ASSERT_GT(new_serial, serial);
char value[PROP_VALUE_MAX];
ASSERT_EQ(6, system_properties.Get("property", value));
ASSERT_STREQ("value2", value);
thread.join();
#else // __BIONIC__
GTEST_SKIP() << "bionic-only test";
#endif // __BIONIC__
}
class KilledByFault {
public:
explicit KilledByFault() {};
bool operator()(int exit_status) const;
};
bool KilledByFault::operator()(int exit_status) const {
return WIFSIGNALED(exit_status) &&
(WTERMSIG(exit_status) == SIGSEGV ||
WTERMSIG(exit_status) == SIGBUS ||
WTERMSIG(exit_status) == SIGABRT);
}
class properties_DeathTest : public BionicDeathTest {};
TEST_F(properties_DeathTest, read_only) {
#if defined(__BIONIC__)
// This test only makes sense if we're talking to the real system property service.
struct stat sb;
ASSERT_FALSE(stat(PROP_FILENAME, &sb) == -1 && errno == ENOENT);
ASSERT_EXIT(__system_property_add("property", 8, "value", 5), KilledByFault(), "");
#else // __BIONIC__
GTEST_SKIP() << "bionic-only test";
#endif // __BIONIC__
}
TEST(properties, __system_property_extra_long_read_only) {
#if defined(__BIONIC__)
SystemPropertiesTest system_properties;
ASSERT_TRUE(system_properties.valid());
std::vector<std::pair<std::string, std::string>> short_properties = {
{ "ro.0char", std::string() },
{ "ro.50char", std::string(50, 'x') },
{ "ro.91char", std::string(91, 'x') },
};
std::vector<std::pair<std::string, std::string>> long_properties = {
{ "ro.92char", std::string(92, 'x') },
{ "ro.93char", std::string(93, 'x') },
{ "ro.1000char", std::string(1000, 'x') },
};
for (const auto& property : short_properties) {
const std::string& name = property.first;
const std::string& value = property.second;
ASSERT_EQ(0, system_properties.Add(name.c_str(), name.size(), value.c_str(), value.size()));
}
for (const auto& property : long_properties) {
const std::string& name = property.first;
const std::string& value = property.second;
ASSERT_EQ(0, system_properties.Add(name.c_str(), name.size(), value.c_str(), value.size()));
}
auto check_with_legacy_read = [&system_properties](const std::string& name,
const std::string& expected_value) {
char value[PROP_VALUE_MAX];
EXPECT_EQ(static_cast<int>(expected_value.size()), system_properties.Get(name.c_str(), value))
<< name;
EXPECT_EQ(expected_value, value) << name;
};
auto check_with_read_callback = [&system_properties](const std::string& name,
const std::string& expected_value) {
const prop_info* pi = system_properties.Find(name.c_str());
ASSERT_NE(nullptr, pi);
std::string value;
system_properties.ReadCallback(pi,
[](void* cookie, const char*, const char* value, uint32_t) {
auto* out_value = reinterpret_cast<std::string*>(cookie);
*out_value = value;
},
&value);
EXPECT_EQ(expected_value, value) << name;
};
for (const auto& property : short_properties) {
const std::string& name = property.first;
const std::string& value = property.second;
check_with_legacy_read(name, value);
check_with_read_callback(name, value);
}
constexpr static const char* kExtraLongLegacyError =
"Must use __system_property_read_callback() to read";
for (const auto& property : long_properties) {
const std::string& name = property.first;
const std::string& value = property.second;
check_with_legacy_read(name, kExtraLongLegacyError);
check_with_read_callback(name, value);
}
#else // __BIONIC__
GTEST_SKIP() << "bionic-only test";
#endif // __BIONIC__
}
// pa_size is 128 * 1024 currently, if a property is longer then we expect it to fail gracefully.
TEST(properties, __system_property_extra_long_read_only_too_long) {
#if defined(__BIONIC__)
SystemPropertiesTest system_properties;
ASSERT_TRUE(system_properties.valid());
auto name = "ro.super_long_property"s;
auto value = std::string(128 * 1024 + 1, 'x');
ASSERT_NE(0, system_properties.Add(name.c_str(), name.size(), value.c_str(), value.size()));
#else // __BIONIC__
GTEST_SKIP() << "bionic-only test";
#endif // __BIONIC__
}