1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #include <string> 6 #include <vector> 7 8 #include "base/strings/string_number_conversions.h" 9 #include "base/threading/platform_thread.h" 10 #include "ppapi/c/pp_var.h" 11 #include "ppapi/c/ppb_var.h" 12 #include "ppapi/proxy/ppapi_proxy_test.h" 13 #include "ppapi/shared_impl/ppb_var_shared.h" 14 15 namespace { 16 std::string VarToString(const PP_Var& var, const PPB_Var* ppb_var) { 17 uint32_t len = 0; 18 const char* utf8 = ppb_var->VarToUtf8(var, &len); 19 return std::string(utf8, len); 20 } 21 const size_t kNumStrings = 100; 22 const size_t kNumThreads = 20; 23 const int kRefsToAdd = 20; 24 } // namespace 25 26 namespace ppapi { 27 namespace proxy { 28 29 class PPB_VarTest : public PluginProxyTest { 30 public: 31 PPB_VarTest() 32 : test_strings_(kNumStrings), vars_(kNumStrings), 33 ppb_var_(ppapi::PPB_Var_Shared::GetVarInterface1_1()) { 34 // Set the value of test_strings_[i] to "i". 35 for (size_t i = 0; i < kNumStrings; ++i) 36 test_strings_[i] = base::IntToString(i); 37 } 38 protected: 39 std::vector<std::string> test_strings_; 40 std::vector<PP_Var> vars_; 41 const PPB_Var* ppb_var_; 42 }; 43 44 // Test basic String operations. 45 TEST_F(PPB_VarTest, Strings) { 46 for (size_t i = 0; i < kNumStrings; ++i) { 47 vars_[i] = ppb_var_->VarFromUtf8(test_strings_[i].c_str(), 48 test_strings_[i].length()); 49 EXPECT_EQ(test_strings_[i], VarToString(vars_[i], ppb_var_)); 50 } 51 // At this point, they should each have a ref count of 1. Add some more. 52 for (int ref = 0; ref < kRefsToAdd; ++ref) { 53 for (size_t i = 0; i < kNumStrings; ++i) { 54 ppb_var_->AddRef(vars_[i]); 55 // Make sure the string is still there with the right value. 56 EXPECT_EQ(test_strings_[i], VarToString(vars_[i], ppb_var_)); 57 } 58 } 59 for (int ref = 0; ref < kRefsToAdd; ++ref) { 60 for (size_t i = 0; i < kNumStrings; ++i) { 61 ppb_var_->Release(vars_[i]); 62 // Make sure the string is still there with the right value. 63 EXPECT_EQ(test_strings_[i], VarToString(vars_[i], ppb_var_)); 64 } 65 } 66 // Now remove the ref counts for each string and make sure they are gone. 67 for (size_t i = 0; i < kNumStrings; ++i) { 68 ppb_var_->Release(vars_[i]); 69 uint32_t len = 10; 70 const char* utf8 = ppb_var_->VarToUtf8(vars_[i], &len); 71 EXPECT_EQ(NULL, utf8); 72 EXPECT_EQ(0u, len); 73 } 74 } 75 76 // PPB_VarTest.Threads tests string operations accessed by multiple threads. 77 namespace { 78 // These three delegate classes which precede the test are for use with 79 // PlatformThread. The test goes roughly like this: 80 // 1) Spawn kNumThreads 'CreateVar' threads, giving each a roughly equal subset 81 // of test_strings_ to 'create'. Each 'CreateVar' thread also converts its 82 // set of vars back in to strings so that the main test thread can verify 83 // their values were correctly converted. 84 // 2) Spawn kNumThreads 'ChangeRefVar' threads. Each of these threads will 85 // incremement & decrement the reference count of ALL vars kRefsToAdd times. 86 // Finally, each thread adds 1 ref count. This leaves each var with a ref- 87 // count of |kNumThreads + 1|. The main test thread removes a ref, leaving 88 // each var with a ref-count of |kNumThreads|. 89 // 3) Spawn kNumThreads 'RemoveVar' threads. Each of these threads releases each 90 // var once. Once all the threads have finished, there should be no vars 91 // left. 92 class CreateVarThreadDelegate : public base::PlatformThread::Delegate { 93 public: 94 // |strings_in|, |vars|, and |strings_out| are arrays, and size is their size. 95 // For each |strings_in[i]|, we will set |vars[i]| using that value. Then we 96 // read the var back out to |strings_out[i]|. 97 CreateVarThreadDelegate(PP_Module pp_module, const std::string* strings_in, 98 PP_Var* vars_out, std::string* strings_out, 99 size_t size) 100 : pp_module_(pp_module), strings_in_(strings_in), vars_out_(vars_out), 101 strings_out_(strings_out), size_(size) { 102 } 103 virtual ~CreateVarThreadDelegate() {} 104 virtual void ThreadMain() { 105 const PPB_Var* ppb_var = ppapi::PPB_Var_Shared::GetVarInterface1_1(); 106 for (size_t i = 0; i < size_; ++i) { 107 vars_out_[i] = ppb_var->VarFromUtf8(strings_in_[i].c_str(), 108 strings_in_[i].length()); 109 strings_out_[i] = VarToString(vars_out_[i], ppb_var); 110 } 111 } 112 private: 113 PP_Module pp_module_; 114 const std::string* strings_in_; 115 PP_Var* vars_out_; 116 std::string* strings_out_; 117 size_t size_; 118 }; 119 120 // A thread that will increment and decrement the reference count of every var 121 // multiple times. 122 class ChangeRefVarThreadDelegate : public base::PlatformThread::Delegate { 123 public: 124 ChangeRefVarThreadDelegate(const std::vector<PP_Var>& vars) : vars_(vars) { 125 } 126 virtual ~ChangeRefVarThreadDelegate() {} 127 virtual void ThreadMain() { 128 const PPB_Var* ppb_var = ppapi::PPB_Var_Shared::GetVarInterface1_1(); 129 // Increment and decrement the reference count for each var kRefsToAdd 130 // times. Note that we always AddRef once before doing the matching Release, 131 // to ensure that we never accidentally release the last reference. 132 for (int ref = 0; ref < kRefsToAdd; ++ref) { 133 for (size_t i = 0; i < kNumStrings; ++i) { 134 ppb_var->AddRef(vars_[i]); 135 ppb_var->Release(vars_[i]); 136 } 137 } 138 // Now add 1 ref to each Var. The net result is that all Vars will have a 139 // ref-count of (kNumThreads + 1) after this. That will allow us to have all 140 // threads release all vars later. 141 for (size_t i = 0; i < kNumStrings; ++i) { 142 ppb_var->AddRef(vars_[i]); 143 } 144 } 145 private: 146 std::vector<PP_Var> vars_; 147 }; 148 149 // A thread that will decrement the reference count of every var once. 150 class RemoveRefVarThreadDelegate : public base::PlatformThread::Delegate { 151 public: 152 RemoveRefVarThreadDelegate(const std::vector<PP_Var>& vars) : vars_(vars) { 153 } 154 virtual ~RemoveRefVarThreadDelegate() {} 155 virtual void ThreadMain() { 156 const PPB_Var* ppb_var = ppapi::PPB_Var_Shared::GetVarInterface1_1(); 157 for (size_t i = 0; i < kNumStrings; ++i) { 158 ppb_var->Release(vars_[i]); 159 } 160 } 161 private: 162 std::vector<PP_Var> vars_; 163 }; 164 165 } // namespace 166 167 TEST_F(PPB_VarTest, Threads) { 168 std::vector<base::PlatformThreadHandle> create_var_threads(kNumThreads); 169 std::vector<CreateVarThreadDelegate> create_var_delegates; 170 // The strings that the threads will re-extract from Vars (so we can check 171 // that they match the original strings). 172 std::vector<std::string> strings_out(kNumStrings); 173 size_t strings_per_thread = kNumStrings/kNumThreads; 174 // Give each thread an equal slice of strings to turn in to vars. (Except the 175 // last thread may get fewer if kNumStrings is not evenly divisible by 176 // kNumThreads). 177 for (size_t slice_start= 0; slice_start < kNumStrings; 178 slice_start += strings_per_thread) { 179 create_var_delegates.push_back( 180 CreateVarThreadDelegate(pp_module(), 181 &test_strings_[slice_start], 182 &vars_[slice_start], 183 &strings_out[slice_start], 184 std::min(strings_per_thread, 185 kNumStrings - slice_start))); 186 } 187 // Now run then join all the threads. 188 for (size_t i = 0; i < kNumThreads; ++i) 189 base::PlatformThread::Create(0, &create_var_delegates[i], 190 &create_var_threads[i]); 191 for (size_t i = 0; i < kNumThreads; ++i) 192 base::PlatformThread::Join(create_var_threads[i]); 193 // Now check that the strings have the expected values. 194 EXPECT_EQ(test_strings_, strings_out); 195 196 // Tinker with the reference counts in a multithreaded way. 197 std::vector<base::PlatformThreadHandle> change_ref_var_threads(kNumThreads); 198 std::vector<ChangeRefVarThreadDelegate> change_ref_var_delegates; 199 for (size_t i = 0; i < kNumThreads; ++i) 200 change_ref_var_delegates.push_back(ChangeRefVarThreadDelegate(vars_)); 201 for (size_t i = 0; i < kNumThreads; ++i) { 202 base::PlatformThread::Create(0, &change_ref_var_delegates[i], 203 &change_ref_var_threads[i]); 204 } 205 for (size_t i = 0; i < kNumThreads; ++i) 206 base::PlatformThread::Join(change_ref_var_threads[i]); 207 208 // Now each var has a refcount of (kNumThreads + 1). Let's decrement each var 209 // once so that every 'RemoveRef' thread (spawned below) owns 1 reference, and 210 // when the last one removes a ref, the Var will be deleted. 211 for (size_t i = 0; i < kNumStrings; ++i) { 212 ppb_var_->Release(vars_[i]); 213 } 214 215 // Check that all vars are still valid and have the values we expect. 216 for (size_t i = 0; i < kNumStrings; ++i) 217 EXPECT_EQ(test_strings_[i], VarToString(vars_[i], ppb_var_)); 218 219 // Remove the last reference counts for all vars. 220 std::vector<base::PlatformThreadHandle> remove_ref_var_threads(kNumThreads); 221 std::vector<RemoveRefVarThreadDelegate> remove_ref_var_delegates; 222 for (size_t i = 0; i < kNumThreads; ++i) 223 remove_ref_var_delegates.push_back(RemoveRefVarThreadDelegate(vars_)); 224 for (size_t i = 0; i < kNumThreads; ++i) { 225 base::PlatformThread::Create(0, &remove_ref_var_delegates[i], 226 &remove_ref_var_threads[i]); 227 } 228 for (size_t i = 0; i < kNumThreads; ++i) 229 base::PlatformThread::Join(remove_ref_var_threads[i]); 230 231 // All the vars should no longer represent valid strings. 232 for (size_t i = 0; i < kNumStrings; ++i) { 233 uint32_t len = 10; 234 const char* utf8 = ppb_var_->VarToUtf8(vars_[i], &len); 235 EXPECT_EQ(NULL, utf8); 236 EXPECT_EQ(0u, len); 237 } 238 } 239 240 } // namespace proxy 241 } // namespace ppapi 242