├── .gitignore ├── Fastcache.h ├── LICENSE ├── Makefile ├── NOTICE ├── README.md ├── example.cpp ├── run_tests.pl └── test ├── TestClass.h ├── test_1.cpp ├── test_2.cpp ├── test_3.cpp ├── test_4.cpp ├── test_5.cpp └── test_6.cpp /.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | *.cfg 3 | test/test_1 4 | test/test_2 5 | test/test_3 6 | test/test_4 7 | test/test_5 8 | test/*.o 9 | example 10 | .DS_Store 11 | -------------------------------------------------------------------------------- /Fastcache.h: -------------------------------------------------------------------------------- 1 | 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | //#include 12 | #include 13 | #include 14 | //#include 15 | #include 16 | 17 | // Shard size. This should be much larger than the number of threads likely to access the cache at any one time. 18 | #ifndef FASTCACHE_SHARDSIZE 19 | #define FASTCACHE_SHARDSIZE 256u 20 | #endif 21 | 22 | #ifndef FASTCACHE_CURATOR_SLEEP_MS 23 | #define FASTCACHE_CURATOR_SLEEP_MS 30000u 24 | #endif 25 | 26 | using boost::shared_ptr; 27 | using boost::mutex; 28 | 29 | 30 | namespace active911 { 31 | 32 | // Write modes 33 | enum fastcache_writemode { 34 | 35 | FASTCACHE_WRITEMODE_WRITE_ALWAYS, 36 | FASTCACHE_WRITEMODE_ONLY_WRITE_IF_SET, 37 | FASTCACHE_WRITEMODE_ONLY_WRITE_IF_NOT_SET 38 | }; 39 | 40 | 41 | struct FastcacheObjectLocked : std::exception { 42 | 43 | char const* what() const throw() { 44 | 45 | return "Object is currently locked"; 46 | }; 47 | }; 48 | 49 | template 50 | class Fastcache { 51 | 52 | /** 53 | * CacheItem 54 | * A wrapper class for cache values 55 | */ 56 | template 57 | class CacheItem { 58 | public: 59 | 60 | CacheItem(shared_ptr data, time_t expiration){ 61 | 62 | this->data=data; 63 | this->expiration=expiration; 64 | }; 65 | 66 | /** 67 | * Have we expired? 68 | * 69 | * @retval bool 70 | */ 71 | bool expired(){ 72 | 73 | // If we have no expiration, the answer is easy 74 | if(this->expiration==0) { 75 | 76 | return false; 77 | } 78 | 79 | // Get the time and compare 80 | struct timespec time; 81 | clock_gettime(CLOCK_REALTIME, &time); 82 | return (time.tv_sec > this->expiration); 83 | }; 84 | 85 | shared_ptr data; 86 | time_t expiration; 87 | }; 88 | 89 | template // Keep compiler happy... really will be T 90 | class Shard { 91 | public: 92 | Shard(){ 93 | 94 | this->guard=shared_ptr(new mutex()); 95 | }; 96 | 97 | void cull_expired_keys() { 98 | 99 | // Iterate map. Somehow this is ok to do even though we are deleting stuff? 100 | for(typename std::map > >::iterator it=this->map.begin();it != this->map.end();/* no increment */){ 101 | 102 | shared_ptr >item=it->second; 103 | 104 | if(item->expired()){ 105 | 106 | this->map.erase(it++); 107 | 108 | } else { 109 | 110 | ++it; 111 | } 112 | } 113 | 114 | } 115 | 116 | shared_ptr guard; 117 | std::map > > map; 118 | }; 119 | 120 | public: 121 | 122 | Fastcache(){ 123 | 124 | // We are making a new cache. Init our shards. 125 | this->shards.reserve(FASTCACHE_SHARDSIZE); 126 | for(unsigned int n=0; n >p (new Shard()); 129 | this->shards.push_back(p); 130 | } 131 | 132 | // Start up the curator thread 133 | this->curator_run=shared_ptr(new boost::detail::atomic_count(1)); 134 | this->curator=shared_ptr(new boost::thread(&Fastcache::curate, this)); 135 | }; 136 | 137 | ~Fastcache(){ 138 | 139 | // Retire the curator 140 | --(*this->curator_run); 141 | this->curator->interrupt(); 142 | this->curator->join(); 143 | 144 | }; 145 | 146 | /** 147 | * Get some metrics 148 | * 149 | * @retval 150 | */ 151 | size_t metrics() { 152 | 153 | size_t total_size=0; 154 | 155 | // Iterate all objects in cache 156 | for(typename std::vector > >::iterator it=this->shards.begin(); it != this->shards.end(); ++it) { 157 | 158 | shared_ptr >shard=*it; 159 | 160 | { // Scope for lock 161 | mutex::scoped_lock lock(*shard->guard); 162 | 163 | // tally 164 | total_size+=shard->map.size(); 165 | } 166 | 167 | } 168 | 169 | return total_size; 170 | 171 | }; 172 | 173 | /** 174 | * Set a value into the cache 175 | * 176 | * @param id the key 177 | * @param val shared_ptr to the object to set 178 | * @param expiration UNIX timestamp 179 | * @param mode the write mode 180 | * @retval number of items written 181 | */ 182 | size_t set(Key id, shared_ptr val, time_t expiration=0, const fastcache_writemode mode=FASTCACHE_WRITEMODE_WRITE_ALWAYS){ 183 | 184 | // Get shard 185 | size_t index=this->calc_index(id); 186 | shared_ptr >shard=this->shards.at(index); 187 | 188 | shared_ptr >item=shared_ptr >(new CacheItem(val, expiration)); 189 | 190 | // Lock and write 191 | mutex::scoped_lock lock(*shard->guard); 192 | #ifdef FASTCACHE_SLOW 193 | sleep(1); 194 | #endif 195 | 196 | if(mode==FASTCACHE_WRITEMODE_ONLY_WRITE_IF_SET) { 197 | 198 | if(shard->map.find(id) == shard->map.end()) { 199 | 200 | // Key not found. Return. 201 | return 0; 202 | 203 | } else { 204 | 205 | // Erase it so we can set it below 206 | shard->map.erase(id); 207 | } 208 | } 209 | 210 | std::pair > >::iterator,bool>result; 211 | result=shard->map.insert(std::pair > >(id,item)); //TODO... use .emplace() once we have C++11 !! (may be faster) 212 | if(!result.second) { 213 | 214 | // Key exists, so nothing was written 215 | if(mode==FASTCACHE_WRITEMODE_ONLY_WRITE_IF_NOT_SET){ 216 | 217 | return 0; 218 | 219 | } else { 220 | 221 | // Erase and re-write 222 | shard->map.erase(id); 223 | shard->map.insert(std::pair > >(id,item)); 224 | return 1; 225 | } 226 | } 227 | 228 | return 1; 229 | }; 230 | 231 | /** 232 | * Find if a key exists 233 | * 234 | * @param id the key 235 | * @retval 1 if the key exists, 0 otherwise 236 | */ 237 | size_t exists(Key id){ 238 | 239 | return (this->get(id))?1:0; // So we don't get false positives on expired keys 240 | 241 | }; 242 | 243 | /** 244 | * Delete a value from the cache 245 | * 246 | * @param id the key 247 | * @retval the number of items erased 248 | */ 249 | size_t del(Key id){ 250 | 251 | // Get shard 252 | size_t index=this->calc_index(id); 253 | shared_ptr >shard=this->shards.at(index); 254 | 255 | // Lock and erase 256 | mutex::scoped_lock lock(*shard->guard); 257 | return shard->map.erase(id); 258 | 259 | }; 260 | 261 | /** 262 | * Get a value from the cache 263 | * 264 | * Does not throw for invalid keys (returns empty pointer) 265 | * 266 | * @param id the key 267 | * @retval boost::shared_ptr. ==empty pointer if nonexistent or expired. 268 | * @throws FastcacheObjectLocked if #FASTCACHE_MUTABLE_DATA is set and object is in use 269 | */ 270 | shared_ptr get(Key id){ 271 | 272 | // Get shard 273 | size_t index=this->calc_index(id); 274 | shared_ptr >shard=this->shards.at(index); 275 | 276 | // Lock 277 | mutex::scoped_lock lock(*shard->guard); 278 | 279 | // Delay if in slow mode... 280 | #ifdef FASTCACHE_SLOW 281 | sleep(1); 282 | #endif 283 | 284 | 285 | // OK, we now have exclusive access to the shard. So no race condition is possible for the affections of this item... 286 | shared_ptr >item; 287 | 288 | try { 289 | 290 | item=shard->map.at(id); // Will throw std::out_of_range if not there! 291 | 292 | // Check for expired 293 | if(item->expired()){ 294 | 295 | // It's expired. Erase it and return empty. 296 | shard->map.erase(id); 297 | return shared_ptr(); 298 | } 299 | 300 | } catch (std::exception& e) { 301 | 302 | return shared_ptr(); // Return empty since it wasn't found 303 | } 304 | 305 | // If we are allowing mutables, make sure no one else is using this data! 306 | #ifdef FASTCACHE_MUTABLE_DATA 307 | if(!item->data.unique()){ 308 | 309 | throw FastcacheObjectLocked(); 310 | } 311 | #endif 312 | 313 | return item->data; 314 | }; 315 | 316 | protected: 317 | 318 | /** 319 | * We are the curator 320 | * 321 | * Purge expired keys, etc 322 | */ 323 | void curate(){ 324 | 325 | while(*this->curator_run) { 326 | 327 | try { 328 | 329 | // Snooze a little 330 | boost::this_thread::sleep(boost::posix_time::milliseconds(FASTCACHE_CURATOR_SLEEP_MS)); 331 | 332 | // Iterate all objects in cache, checking for expired objects 333 | for(typename std::vector > >::iterator it=this->shards.begin(); it != this->shards.end(); ++it) { 334 | 335 | shared_ptr >shard=*it; 336 | 337 | { // Scope for lock 338 | mutex::scoped_lock lock(*shard->guard); 339 | 340 | // Cull expired keys 341 | shard->cull_expired_keys(); 342 | } 343 | 344 | } 345 | 346 | } catch(boost::thread_interrupted& e) { 347 | 348 | // We were asked to leave? 349 | return; 350 | 351 | } catch(std::exception& e) { 352 | 353 | // TODO... something bad happened in the curator thread 354 | } 355 | } 356 | }; 357 | 358 | 359 | /** 360 | * Calculate the shard index 361 | * 362 | * It is important that this function has a repeatable but otherwise randomish (uniform) output 363 | * We use the boost hash, "a TR1 compliant hash function object" 364 | * 365 | * @param id 366 | */ 367 | size_t calc_index(Key id){ 368 | 369 | return (size_t) this->hash(id) % FASTCACHE_SHARDSIZE; 370 | }; 371 | 372 | 373 | boost::hashhash; 374 | std::vector > > shards; 375 | shared_ptrcurator; 376 | shared_ptrcurator_run; 377 | 378 | 379 | }; 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | #GEN_SRC := tests.cpp example.cpp 2 | DEP_HEAD := Fastcache.h 3 | #gGEN_OBJ := $(patsubst %.cpp,%.o, $(GEN_SRC)) 4 | 5 | #THRIFT_DIR := /usr/local/include/thrift 6 | #INC := -I$(THRIFT_DIR) -Igen-cpp/ -Iinclude/ -Iinih/ -Iinih/cpp/ 7 | 8 | all: test example 9 | 10 | 11 | test: test/test_1 test/test_2 test/test_3 test/test_4 test/test_5 test/test_6 12 | /usr/bin/perl run_tests.pl 13 | 14 | example: example.o 15 | $(CXX) $^ -o $@ -L/usr/local/lib -lboost_thread 16 | 17 | test/test_%: test/test_%.o 18 | $(CXX) $^ -o $@ -L/usr/local/lib -lboost_thread 19 | 20 | test/%.o: test/%.cpp $(DEP_HEAD) 21 | $(CXX) -Wall $(INC) -c $< -o $@ 22 | 23 | %.o: %.cpp $(DEP_HEAD) 24 | $(CXX) -Wall $(INC) -c $< -o $@ 25 | 26 | 27 | clean: 28 | $(RM) *.o test/*.o test/test_1 test/test_2 test/test_3 test/test_4 test/test_5 test/test_6 example 29 | 30 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | fastcache : Wicked fast, thread safe in-memory key/object store for C++ 2 | Copyright 2013 Active911 Inc. 3 | Originally authored by Joseph Sullivan in Oregon, USA -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## fastcache 2 | 3 | Wicked fast, thread safe in-memory key/object store for C++ 4 | 5 | ``` 6 | Testing with 30 simultaneous threads 7 | Object size is 16 bytes 8 | Loading up cache...30000 objects stored in 0.041198 sec 9 | Reading cache...30000 objects read in 0.0038482 sec 10 | ``` 11 | 12 | ### Features 13 | 14 | - Did I mention fast? 15 | - Thread safe 16 | - Apache 2.0 license 17 | 18 | ### Introduction 19 | 20 | Sometimes using an external caching solution like memcached or Redis is just unnecessary. 21 | 22 | What! 23 | 24 | No, think about it. Key/value stores are great if... 25 | 26 | - You need to access cached data from several different forward-facing servers 27 | - Your code is written in PHP or something non-persistent (request based) 28 | - You clustering 29 | 30 | On the other hand, what if you... 31 | 32 | - Want to store entire objects natively 33 | - Know exactly what kind of data you are caching 34 | - Only need your cache available to one API (like a Thrift server daemon) 35 | - Need multiple worker threads to access the cache 36 | - Want supreme performance 37 | 38 | 39 | ### Impetus 40 | 41 | We wrote a miultithreaded server using Apache Thrift in C++. We then realized that maintaining several connections to Redis was a little silly, because we were working within the framework of a native, persistent, C++ daemon that was already the unified access point for all our API calls. In short, we had access to the same raw resources as Redis did. And unlike Redis, we could allow all our happy worker threads to access the data at the same time (Redis is effectively single threaded, BTW). 42 | 43 | 44 | ### Design 45 | 46 | fastcache is a template class. So you can store anything in it, and you can use as a key anything that boost::hash accepts (like int or std::string). If you replace the hashing function with your own, you can even do away with that requirement. 47 | 48 | The design challenge was to not just store data, but to do so in a way that multiple threads could access it concurrently with a minimum of blocking. Obviously, we could use a single large std::map and then mutex it, but we would have a ton of threads all waiting on each other. Instead, we shard the cache into M maps. We then use a hash function to calculate which map (shard) will hold a particular piece of data. For T concurrent threads reading or writing data, we will have (mostly) non blocking operation as long as our hash generates an even distribution and M >> T. T defaults to 256 but can be changed by setting FASTCACHE_SHARDSIZE. 49 | 50 | Objects are passed to and from the cache wrapped in boost::shared_ptr. Create all cache objects using boost::shared_ptr and then let them go out of scope as soon as you set them. 51 | 52 | When in mutable mode (see below), we use the ```.unique()``` method of boost::shared_ptr in order to see if we have a lock. **When operating in this mode, do not add the same object to the cache twice!** Doing so will make the cache think the object is in use. You will then get a FastcacheObjectLocked exception every time you attempt to ```.get()``` it. 53 | 54 | ### Operational modes 55 | 56 | The cache runs in one of two modes, depending on whether you ```#define FASTCACHE_MUTABLE_DATA```: 57 | 58 | - Default mode - This means that you can get an object from the cache and hold onto it as long as you like without fear of blocking other threads. If the object is changed in the meantime (i.e., replaced - actual mutations within the cache are not supported!) your copy will remain safe. You cannot mutate the object itself. This is the faster of the two modes 59 | - Mutable mode, where ```FASTCACHE_MUTABLE_DATA``` is defined. In this mode, you have a lock on the data as long as your share_ptr is valid. The lock does not keep the object from being removed from the cache, it just means you have an exclusive read/write lock on the object itself. Another attempt to read the data will result in a FastcacheObjectLocked exception. Since the shard is locked while a ```get()``` operation is taking place, Fastcache itself cannot just block in this case (it would block all operations on the entire shard!). It is YOUR responsibility in this case to ```catch()``` the exception, sleep, retry, etc. In mutable mode, you must be careful to make sure object references go out of scope as soon as possible so you don't block other requests. Obviously, this mode will be a little slower than non-mutable mode. 60 | 61 | Again, DON'T MUTATE anything you put into or take out of the cache, unless you have #defined ```FASTCACHE_MUTABLE_DATA```!! This is very important to avoid trouble with multiple threads. When you get something from the cache, make a copy of the data (not just the pointer) before you do anything except read it 62 | 63 | 64 | ### Example 65 | 66 | ```cpp 67 | #include 68 | #include 69 | #include 70 | #include "Fastcache.h" 71 | 72 | using namespace std; 73 | using namespace active911; 74 | using boost::shared_ptr; 75 | 76 | class TestClass { 77 | public: 78 | int id; 79 | string name; 80 | }; 81 | 82 | int main(int argc, char **argv) { 83 | 84 | // Make data for cache 85 | shared_ptrin=shared_ptr(new TestClass()); 86 | in->id=23; 87 | in->name="My name"; 88 | 89 | // Create cache as string->TestClass store. Add data 90 | Fastcachecache; 91 | cache.set("MyDataKey",in); 92 | 93 | // Fetch back 94 | shared_ptrout=cache.get("MyDataKey"); 95 | 96 | cout << out->id << ":" << out->name << endl; 97 | 98 | return 0; 99 | } 100 | ``` 101 | 102 | ### Installation 103 | 104 | On Debian, you will need to run the following 105 | ``` 106 | apt-get install build-essential libboost-dev libboost-thread-dev 107 | ldconfig 108 | ``` 109 | then you can run ```make``` 110 | 111 | ### Configuration 112 | 113 | - setting FASTCACHE_CURATOR_SLEEP_MS will change how often the curator thread collects garbage. This won't have an effect on key expiration, it just means it will be a little lazier with the memory usage (++ performance though). The default setting is 30000 (30 seconds). If your cache is large, you might make this number larger since it has to traverse the whole cache every time it does its job. 114 | - FASTCACHE_SHARDSIZE may need to be increased if you have a large number of threads accessing the cache. It will use only a little more memory, but should really help in keeping shard contention low. Make sure you only set it to a power of two so hash modulus can be calculated easily! 115 | 116 | 117 | 118 | 119 | 120 | -------------------------------------------------------------------------------- /example.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "Fastcache.h" 5 | 6 | using namespace std; 7 | using namespace active911; 8 | using boost::shared_ptr; 9 | 10 | class TestClass { 11 | public: 12 | int id; 13 | string name; 14 | }; 15 | 16 | int main(int argc, char **argv) { 17 | 18 | // Make data for cache 19 | shared_ptrin=shared_ptr(new TestClass()); 20 | in->id=23; 21 | in->name="My name"; 22 | 23 | 24 | // Create cache as string->TestClass store. Add data 25 | Fastcachecache; 26 | cache.set("MyDataKey",in); 27 | 28 | 29 | // Fetch back 30 | shared_ptrout=cache.get("MyDataKey"); 31 | 32 | cout << out->id << ":" << out->name << endl; 33 | 34 | return 0; 35 | } -------------------------------------------------------------------------------- /run_tests.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | use strict; 4 | use warnings; 5 | 6 | $|=1; # Autoflush 7 | 8 | print("\nRUNNING TESTS\n"); 9 | print("---------------\n"); 10 | 11 | # Find the tests in this folder 12 | opendir(DIR, "./test/") or die ("Unable to read tests from current directory"); 13 | my @tests; 14 | 15 | while(my $filename=readdir(DIR)){ 16 | 17 | if($filename=~/^test_(\d)$/) { 18 | 19 | push @tests, $1; 20 | } 21 | } 22 | 23 | scalar @tests or die ("No tests found - perhaps you need to 'make tests' first?"); 24 | 25 | # Run each test 26 | my $completed_tests=0; 27 | for my $test (@tests) { 28 | 29 | print ("-- Test $test\n"); 30 | print `./test/test_$test`; 31 | 32 | if($? != 0) { 33 | 34 | print ("************ FAIL\n\n"); 35 | die("Test $test returned nonzero\n"); 36 | 37 | } 38 | 39 | $completed_tests++; 40 | } 41 | 42 | # Success 43 | print("**************************\n"); 44 | print("* SUCCESS! $completed_tests/".scalar(@tests)." tests OK. *\n"); 45 | print("**************************\n"); 46 | 47 | -------------------------------------------------------------------------------- /test/TestClass.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Test class 3 | * 4 | */ 5 | 6 | #include 7 | 8 | using namespace std; 9 | 10 | class TestClass { 11 | public: 12 | TestClass() { }; 13 | TestClass(string str) { this->name=str; }; 14 | int id; 15 | string name; 16 | 17 | #ifndef FASTCACHE_TEST_SMALL 18 | char mem[1008]; // Make it large 19 | #endif 20 | }; -------------------------------------------------------------------------------- /test/test_1.cpp: -------------------------------------------------------------------------------- 1 | 2 | // Test1 3 | // Prove that the mutexes are working by causing the cache to block while reading. 4 | // Read from the cache using 2 threads and observe the delay as they wait for each other. 5 | 6 | // Slow down the cache 7 | #define FASTCACHE_SLOW 8 | 9 | // Run with 3 threads 10 | #define THREAD_COUNT 3 11 | 12 | // Clock source 13 | #define CLOCKSRC CLOCK_MONOTONIC_RAW 14 | 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include "../Fastcache.h" 21 | #include "TestClass.h" 22 | #include 23 | 24 | using namespace std; 25 | using namespace active911; 26 | using boost::shared_ptr; 27 | using boost::thread; 28 | 29 | 30 | 31 | // Counter for thread runloop 32 | boost::detail::atomic_count run(1); 33 | 34 | // The cache 35 | Fastcachecache; 36 | 37 | // Timer 38 | struct timespec start; 39 | struct timespec end; 40 | 41 | 42 | void thread_function(int thread_number) { 43 | 44 | // Create a cache object 45 | { 46 | shared_ptrobj=shared_ptr(new TestClass()); 47 | // cout << "Thread " << thread_number << " BEGIN storing data" << endl; 48 | cout << "w" << flush; 49 | cache.set("MyDataKey", obj); 50 | // cout << "Thread " << thread_number << " END storing data" << endl; 51 | cout << "W" << flush; 52 | 53 | // cout << "Thread " << thread_number << " BEGIN reading data" << endl; 54 | cout << "r" << flush; 55 | shared_ptrread=cache.get("MyDataKey"); 56 | // cout << "Thread " << thread_number << " END reading data" << endl; 57 | cout << "R" << flush; 58 | } 59 | 60 | while(run) { sleep(1); } 61 | } 62 | 63 | int main(int argc, char **argv) { 64 | 65 | // Setup cout 66 | cout.precision(5); 67 | 68 | // Start test 69 | cout << "Testing locks..." << flush; 70 | clock_gettime(CLOCKSRC, &start); 71 | 72 | // Start several threads, each accessing the cache 73 | vector > threads; 74 | for(int n=0; n(new boost::thread(&thread_function,n))); 77 | } 78 | 79 | // Join the threads 80 | --run; 81 | for(int n=0; njoin(); 84 | } 85 | 86 | // End the test 87 | clock_gettime(CLOCKSRC, &end); 88 | double start_time=(double)start.tv_sec+((double)start.tv_nsec/1000000000.0); 89 | double end_time=(double)end.tv_sec+((double)end.tv_nsec/1000000000.0); 90 | double elapsed_time=end_time-start_time; 91 | 92 | if(elapsed_time > (THREAD_COUNT*2)-1) { 93 | 94 | cout << "...OK"; 95 | 96 | } else { 97 | 98 | cout << "...FAIL"; 99 | } 100 | 101 | cout << " (" << elapsed_time << " sec)" << endl; 102 | return 0; 103 | } 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | -------------------------------------------------------------------------------- /test/test_2.cpp: -------------------------------------------------------------------------------- 1 | 2 | // Test2 3 | // Key integrity check 4 | // Do 9000 reads and writes, checking key values 5 | // Note: This will be a little slower than it could be since we are doing string manipulations 6 | 7 | 8 | // Run with 30 threads 9 | #define THREAD_COUNT 30 10 | 11 | // Clock source 12 | #define CLOCKSRC CLOCK_MONOTONIC_RAW 13 | 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include "../Fastcache.h" 20 | #include "TestClass.h" 21 | #include 22 | #include 23 | #include 24 | 25 | using namespace std; 26 | using namespace active911; 27 | using boost::shared_ptr; 28 | using boost::thread; 29 | 30 | 31 | 32 | // Counter for thread runloop 33 | boost::detail::atomic_count run(1); 34 | 35 | // The cache 36 | Fastcachecache; 37 | 38 | // Timer 39 | struct timespec start; 40 | struct timespec end; 41 | 42 | 43 | void load_cache(int thread_number) { 44 | 45 | ostringstream s; 46 | 47 | // Create a cache object 48 | for(int n=0; n<300; n++){ 49 | s.str(""); 50 | s << "MyDataKey" << thread_number << "-" << n; 51 | shared_ptrobj=shared_ptr(new TestClass(s.str())); 52 | cache.set(s.str(), obj); 53 | } 54 | 55 | } 56 | 57 | void read_cache(int thread_number) { 58 | 59 | ostringstream s; 60 | 61 | // Read 62 | for(int n=0; n<300; n++){ 63 | s.str(""); 64 | s << "MyDataKey" << thread_number << "-" << n; 65 | 66 | shared_ptrobj=cache.get(s.str()); 67 | assert(obj->name.compare(s.str())==0); 68 | } 69 | 70 | } 71 | 72 | 73 | int main(int argc, char **argv) { 74 | 75 | // Setup cout 76 | cout.precision(5); 77 | 78 | // Start load test 79 | cout << "Object size is " << sizeof(TestClass) << " bytes" << endl; 80 | cout << "Loading up cache..." << flush; 81 | clock_gettime(CLOCKSRC, &start); 82 | 83 | // Start threads 84 | vector > threads; 85 | for(int n=0; n(new boost::thread(&load_cache,n))); 88 | } 89 | 90 | // Join the threads 91 | --run; 92 | for(int n=0; njoin(); 95 | } 96 | 97 | // End the load test 98 | clock_gettime(CLOCKSRC, &end); 99 | double start_time=(double)start.tv_sec+((double)start.tv_nsec/1000000000.0); 100 | double end_time=(double)end.tv_sec+((double)end.tv_nsec/1000000000.0); 101 | double elapsed_time=end_time-start_time; 102 | 103 | cout << ( THREAD_COUNT * 300 ) << " objects stored in " << elapsed_time << " sec" << endl; 104 | threads.clear(); 105 | 106 | 107 | // Start read test 108 | cout << "Reading cache..." << flush; 109 | clock_gettime(CLOCKSRC, &start); 110 | 111 | // Start threads 112 | for(int n=0; n(new boost::thread(&read_cache,n))); 115 | } 116 | 117 | // Join the threads 118 | --run; 119 | for(int n=0; njoin(); 122 | } 123 | 124 | // End the load test 125 | clock_gettime(CLOCKSRC, &end); 126 | start_time=(double)start.tv_sec+((double)start.tv_nsec/1000000000.0); 127 | end_time=(double)end.tv_sec+((double)end.tv_nsec/1000000000.0); 128 | elapsed_time=end_time-start_time; 129 | 130 | cout << ( THREAD_COUNT * 300 ) << " objects read in " << elapsed_time << " sec" << endl; 131 | 132 | 133 | return 0; 134 | } 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | -------------------------------------------------------------------------------- /test/test_3.cpp: -------------------------------------------------------------------------------- 1 | 2 | // Test3 3 | // Speed check 4 | // Do 9000 reads and writes, fast 5 | // This is also a test of using ints for keys 6 | 7 | 8 | // Run with 30 threads 9 | #define THREAD_COUNT 30 10 | #define OBJECT_COUNT 1000 11 | #define FASTCACHE_TEST_SMALL 12 | 13 | // Clock source 14 | #define CLOCKSRC CLOCK_MONOTONIC_RAW 15 | 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include "../Fastcache.h" 22 | #include "TestClass.h" 23 | #include 24 | #include 25 | 26 | using namespace std; 27 | using namespace active911; 28 | using boost::shared_ptr; 29 | using boost::thread; 30 | 31 | 32 | 33 | // Counter for thread runloop 34 | boost::detail::atomic_count run(1); 35 | 36 | // The cache 37 | Fastcachecache; 38 | 39 | // Timer 40 | struct timespec start; 41 | struct timespec end; 42 | 43 | 44 | void load_cache(int thread_number) { 45 | 46 | // Create a cache object 47 | for(int n=0; nobj=shared_ptr(new TestClass()); 51 | cache.set(key, obj); 52 | } 53 | 54 | } 55 | 56 | void read_cache(int thread_number) { 57 | 58 | // Read 59 | for(int n=0; nobj=cache.get(key); 63 | // assert(obj->name.compare(s.str())==0); 64 | } 65 | 66 | } 67 | 68 | 69 | int main(int argc, char **argv) { 70 | 71 | // Setup cout 72 | cout.precision(5); 73 | 74 | // Start load test 75 | cout << "Testing with " << THREAD_COUNT << " simultaneous threads" << endl; 76 | cout << "Object size is " << sizeof(TestClass) << " bytes" << endl; 77 | cout << "Loading up cache..." << flush; 78 | clock_gettime(CLOCKSRC, &start); 79 | 80 | // Start threads 81 | vector > threads; 82 | for(int n=0; n(new boost::thread(&load_cache,n))); 85 | } 86 | 87 | // Join the threads 88 | --run; 89 | for(int n=0; njoin(); 92 | } 93 | 94 | // End the load test 95 | clock_gettime(CLOCKSRC, &end); 96 | double start_time=(double)start.tv_sec+((double)start.tv_nsec/1000000000.0); 97 | double end_time=(double)end.tv_sec+((double)end.tv_nsec/1000000000.0); 98 | double elapsed_time=end_time-start_time; 99 | 100 | cout << ( THREAD_COUNT * OBJECT_COUNT ) << " objects stored in " << elapsed_time << " sec" << endl; 101 | threads.clear(); 102 | 103 | 104 | // Start read test 105 | cout << "Reading cache..." << flush; 106 | clock_gettime(CLOCKSRC, &start); 107 | 108 | // Start threads 109 | for(int n=0; n(new boost::thread(&read_cache,n))); 112 | } 113 | 114 | // Join the threads 115 | --run; 116 | for(int n=0; njoin(); 119 | } 120 | 121 | // End the load test 122 | clock_gettime(CLOCKSRC, &end); 123 | start_time=(double)start.tv_sec+((double)start.tv_nsec/1000000000.0); 124 | end_time=(double)end.tv_sec+((double)end.tv_nsec/1000000000.0); 125 | elapsed_time=end_time-start_time; 126 | 127 | cout << ( THREAD_COUNT * OBJECT_COUNT ) << " objects read in " << elapsed_time << " sec" << endl; 128 | 129 | 130 | return 0; 131 | } 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | -------------------------------------------------------------------------------- /test/test_4.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "../Fastcache.h" 5 | #include "TestClass.h" 6 | #include 7 | 8 | 9 | using namespace std; 10 | using namespace active911; 11 | using boost::shared_ptr; 12 | 13 | 14 | int main(int argc, char **argv) { 15 | 16 | // Make data for cache 17 | shared_ptrin=shared_ptr(new TestClass()); 18 | in->name="My Test Object"; 19 | shared_ptrin2=shared_ptr(new TestClass()); 20 | in2->name="My Test Object 2"; 21 | 22 | // A place to hold cache output 23 | shared_ptrout; 24 | 25 | // Create cache as string->TestClass store. Add data 26 | Fastcachecache; 27 | cache.set("MyDataKey",in); 28 | 29 | // Fetch back, check name 30 | out=cache.get("MyDataKey"); 31 | cout << "Checking simple object retrieval"; 32 | assert(out); 33 | assert(out->name=="My Test Object"); 34 | cout << "...OK" << endl; 35 | 36 | // Try to get a fake key 37 | out=cache.get("NonExistantKey"); 38 | cout << "Checking empty object retrieval"; 39 | assert(!out); 40 | cout << "...OK" << endl; 41 | 42 | // Check set option FASTCACHE_WRITEMODE_ONLY_WRITE_IF_NOT_SET 43 | assert(0==cache.set("MyDataKey",in2, 0, FASTCACHE_WRITEMODE_ONLY_WRITE_IF_NOT_SET)); // Wasn't set 44 | out=cache.get("MyDataKey"); 45 | cout << "Checking FASTCACHE_WRITEMODE_ONLY_WRITE_IF_NOT_SET"; 46 | assert(out); 47 | assert(out->name=="My Test Object"); // Object 1 (wasn't replaced) 48 | cout << "...OK" << endl; 49 | 50 | // Check set option FASTCACHE_WRITEMODE_ONLY_WRITE_IF_SET 51 | assert(0==cache.set("NonExistantKey",in2, 0, FASTCACHE_WRITEMODE_ONLY_WRITE_IF_SET)); // Wasn't set 52 | out=cache.get("NonExistantKey"); 53 | cout << "Checking FASTCACHE_WRITEMODE_ONLY_WRITE_IF_SET"; 54 | assert(!out); // Nothing there 55 | cout << "...OK" << endl; 56 | 57 | // Check set option FASTCACHE_WRITEMODE_WRITE_ALWAYS (default mode) 58 | assert(cache.set("MyDataKey",in2, 0)); 59 | out=cache.get("MyDataKey"); 60 | cout << "Checking FASTCACHE_WRITEMODE_WRITE_ALWAYS"; 61 | assert(out); 62 | assert(out->name=="My Test Object 2"); // Was replaced 63 | cout << "...OK" << endl; 64 | 65 | // Expiration 66 | struct timespec time; 67 | clock_gettime(CLOCK_REALTIME, &time); 68 | assert(cache.set("MyDataKey2", in2, time.tv_sec+1)); // Expires in 1 sec 69 | cout << "Checking cache expiration"; 70 | out=cache.get("MyDataKey2"); 71 | assert(out); 72 | assert(out->name=="My Test Object 2"); 73 | sleep(2); 74 | out=cache.get("MyDataKey2"); 75 | assert(!out); // Was expired 76 | cout << "...OK" << endl; 77 | 78 | // Exists and deletion 79 | cout << "Checking exists()"; 80 | assert(cache.exists("MyDataKey")); 81 | assert(!cache.exists("NonExistantKey")); 82 | cache.del("MyDataKey"); 83 | assert(!cache.exists("MyDataKey")); 84 | cout << "...OK" << endl; 85 | 86 | return 0; 87 | } 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | -------------------------------------------------------------------------------- /test/test_5.cpp: -------------------------------------------------------------------------------- 1 | #define FASTCACHE_CURATOR_SLEEP_MS 100u 2 | 3 | 4 | #include 5 | #include 6 | #include 7 | #include "../Fastcache.h" 8 | #include "TestClass.h" 9 | #include 10 | 11 | 12 | using namespace std; 13 | using namespace active911; 14 | using boost::shared_ptr; 15 | 16 | 17 | int main(int argc, char **argv) { 18 | 19 | // Create cache as string->TestClass store. Add data 20 | Fastcachecache; 21 | 22 | // Get time 23 | struct timespec time; 24 | clock_gettime(CLOCK_REALTIME, &time); 25 | 26 | cout << "Checking cache culling by curator thread"; 27 | 28 | // Insert 100 objects that expire in 1 second 29 | for(int n=0; n<100; n++){ 30 | 31 | cache.set(1000+n,shared_ptr(new TestClass()), time.tv_sec+1); 32 | } 33 | 34 | // And 100 objects that expire in 2 seconds 35 | for(int n=0; n<100; n++){ 36 | 37 | cache.set(2000+n,shared_ptr(new TestClass()), time.tv_sec+2); 38 | } 39 | 40 | // And 100 objects that expire in 3 seconds 41 | for(int n=0; n<100; n++){ 42 | 43 | cache.set(3000+n,shared_ptr(new TestClass()), time.tv_sec+3); 44 | } 45 | 46 | // Check size 47 | int max_tries=6; 48 | size_t size=cache.metrics(); 49 | assert(size==300); 50 | 51 | // Watch it decrement 52 | do { 53 | size=cache.metrics(); 54 | cout << "..." << size << flush; 55 | sleep(1); 56 | 57 | } while (size && max_tries--); 58 | assert(size==0); 59 | 60 | cout << "...OK" << endl; 61 | 62 | return 0; 63 | } 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /test/test_6.cpp: -------------------------------------------------------------------------------- 1 | 2 | // Test FASTCACHE_MUTABLE_DATA mode. 3 | // Read from the cache using 2 threads and make sure we get an exception when the second thread gets a lock. 4 | 5 | 6 | // Run with 3 threads 7 | #define THREAD_COUNT 3 8 | 9 | // Mutable mode 10 | #define FASTCACHE_MUTABLE_DATA 11 | 12 | #include 13 | #include 14 | #include 15 | #include "../Fastcache.h" 16 | #include "TestClass.h" 17 | #include 18 | 19 | using namespace std; 20 | using namespace active911; 21 | using boost::shared_ptr; 22 | 23 | 24 | // The cache 25 | Fastcachecache; 26 | 27 | 28 | 29 | int main(int argc, char **argv) { 30 | 31 | // Start test 32 | cout << "Testing FASTCACHE_MUTABLE_DATA locking..." << flush; 33 | 34 | // Put something in the cache 35 | { 36 | shared_ptrobj=shared_ptr(new TestClass("Tom")); 37 | cache.set("MyDataKey", obj); 38 | } 39 | 40 | // Get it back from the cache, twice (test exception) 41 | { 42 | shared_ptrread=cache.get("MyDataKey"); 43 | assert(read->name=="Tom"); 44 | 45 | // Now get another copy (should throw) 46 | bool exception_thrown=false; 47 | try { 48 | 49 | shared_ptrread2=cache.get("MyDataKey"); 50 | 51 | } catch (FastcacheObjectLocked &e) { 52 | 53 | exception_thrown=true; 54 | } 55 | assert(exception_thrown); 56 | } 57 | 58 | // Now it should be available again 59 | shared_ptrtom=cache.get("MyDataKey"); 60 | assert(tom->name=="Tom"); 61 | cout << "...OK" << endl; 62 | 63 | 64 | // This test should be equivalent, but let's be sure 65 | cout << "Testing FASTCACHE_MUTABLE_DATA throws if set shared_ptr not out of scope..." << flush; 66 | 67 | // Put something in the cache, and read it right away 68 | { 69 | shared_ptrobj=shared_ptr(new TestClass("Dick")); 70 | cache.set("MyDataKey2", obj); 71 | 72 | bool exception_thrown=false; 73 | try { 74 | 75 | shared_ptrread=cache.get("MyDataKey2"); 76 | 77 | } catch (FastcacheObjectLocked &e) { 78 | 79 | exception_thrown=true; 80 | } 81 | assert(exception_thrown); 82 | } 83 | 84 | // Now it should be available again 85 | shared_ptrdick=cache.get("MyDataKey2"); 86 | assert(dick->name=="Dick"); 87 | 88 | cout << "...OK" << endl; 89 | return 0; 90 | } 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | --------------------------------------------------------------------------------