2 * 2012+ Copyright (c) Evgeniy Polyakov <zbr@ioremap.net>
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
21 #include <boost/unordered_map.hpp>
22 #include <boost/shared_array.hpp>
23 #include <boost/shared_ptr.hpp>
24 #include <boost/make_shared.hpp>
25 #include <boost/thread.hpp>
26 #include <boost/intrusive/list.hpp>
27 #include <boost/intrusive/set.hpp>
29 #include "../library/elliptics.h"
31 #include "elliptics/packet.h"
32 #include "elliptics/interface.h"
34 namespace ioremap
{ namespace cache
{
37 key_t(const unsigned char *id
) {
38 memcpy(this->id
, id
, DNET_ID_SIZE
);
41 unsigned char id
[DNET_ID_SIZE
];
44 size_t hash(const unsigned char *id
) {
45 size_t num
= DNET_ID_SIZE
/ sizeof(size_t);
47 size_t *ptr
= (size_t *)id
;
48 size_t hash
= 0x883eaf5a;
49 for (size_t i
= 0; i
< num
; ++i
)
56 std::size_t operator()(const key_t
&key
) const {
57 return ioremap::cache::hash(key
.id
);
62 bool operator() (const key_t
&x
, const key_t
&y
) const {
63 return memcmp(x
.id
, y
.id
, DNET_ID_SIZE
) == 0;
69 raw_data_t(const char *data
, size_t size
) {
71 m_data
.insert(m_data
.begin(), data
, data
+ size
);
74 std::vector
<char> &data(void) {
83 std::vector
<char> m_data
;
86 struct data_lru_tag_t
;
87 typedef boost::intrusive::list_base_hook
<boost::intrusive::tag
<data_lru_tag_t
>,
88 boost::intrusive::link_mode
<boost::intrusive::safe_link
>
89 > lru_list_base_hook_t
;
90 struct data_set_tag_t
;
91 typedef boost::intrusive::set_base_hook
<boost::intrusive::tag
<data_set_tag_t
>,
92 boost::intrusive::link_mode
<boost::intrusive::safe_link
>
95 struct time_set_tag_t
;
96 typedef boost::intrusive::set_base_hook
<boost::intrusive::tag
<time_set_tag_t
>,
97 boost::intrusive::link_mode
<boost::intrusive::safe_link
>
98 > time_set_base_hook_t
;
100 class data_t
: public lru_list_base_hook_t
, public set_base_hook_t
, public time_set_base_hook_t
{
102 data_t(const unsigned char *id
) : m_lifetime(0) {
103 memcpy(m_id
.id
, id
, DNET_ID_SIZE
);
106 data_t(const unsigned char *id
, size_t lifetime
, const char *data
, size_t size
, bool remove_from_disk
) :
107 m_lifetime(0), m_remove_from_disk(remove_from_disk
) {
108 memcpy(m_id
.id
, id
, DNET_ID_SIZE
);
111 m_lifetime
= lifetime
+ time(NULL
);
113 m_data
.reset(new raw_data_t(data
, size
));
119 const struct dnet_raw_id
&id(void) const {
123 boost::shared_ptr
<raw_data_t
> data(void) const {
127 size_t lifetime(void) const {
131 bool remove_from_disk() const {
132 return m_remove_from_disk
;
135 size_t size(void) const {
136 return m_data
->size();
139 friend bool operator< (const data_t
&a
, const data_t
&b
) {
140 return dnet_id_cmp_str(a
.id().id
, b
.id().id
) < 0;
143 friend bool operator> (const data_t
&a
, const data_t
&b
) {
144 return dnet_id_cmp_str(a
.id().id
, b
.id().id
) > 0;
147 friend bool operator== (const data_t
&a
, const data_t
&b
) {
148 return dnet_id_cmp_str(a
.id().id
, b
.id().id
) == 0;
153 bool m_remove_from_disk
;
154 struct dnet_raw_id m_id
;
155 boost::shared_ptr
<raw_data_t
> m_data
;
158 typedef boost::intrusive::list
<data_t
, boost::intrusive::base_hook
<lru_list_base_hook_t
> > lru_list_t
;
159 typedef boost::intrusive::set
<data_t
, boost::intrusive::base_hook
<set_base_hook_t
>,
160 boost::intrusive::compare
<std::less
<data_t
> >
163 struct lifetime_less
{
164 bool operator() (const data_t
&x
, const data_t
&y
) const {
165 return x
.lifetime() < y
.lifetime();
169 typedef boost::intrusive::set
<data_t
, boost::intrusive::base_hook
<time_set_base_hook_t
>,
170 boost::intrusive::compare
<lifetime_less
>
175 cache_t(struct dnet_node
*n
) : m_need_exit(false), m_node(n
), m_cache_size(0), m_max_cache_size(n
->cache_size
) {
176 m_lifecheck
= boost::thread(boost::bind(&cache_t::life_check
, this));
183 while (!m_lru
.empty()) {
184 data_t raw
= m_lru
.front();
189 void write(const unsigned char *id
, size_t lifetime
, const char *data
, size_t size
, bool remove_from_disk
) {
190 boost::mutex::scoped_lock
guard(m_lock
);
192 iset_t::iterator it
= m_set
.find(id
);
193 if (it
!= m_set
.end())
194 erase_element(&(*it
));
196 if (size
+ m_cache_size
> m_max_cache_size
)
200 * nothing throws exception below this 'new' operator, so there is no try/catch block
202 data_t
*raw
= new data_t(id
, lifetime
, data
, size
, remove_from_disk
);
205 m_lru
.push_back(*raw
);
207 m_lifeset
.insert(*raw
);
209 m_cache_size
+= size
;
212 boost::shared_ptr
<raw_data_t
> read(const unsigned char *id
) {
213 boost::mutex::scoped_lock
guard(m_lock
);
215 iset_t::iterator it
= m_set
.find(id
);
216 if (it
== m_set
.end())
217 throw std::runtime_error("no record");
219 m_lru
.erase(m_lru
.iterator_to(*it
));
220 m_lru
.push_back(*it
);
224 bool remove(const unsigned char *id
) {
225 bool removed
= false;
226 bool remove_from_disk
= false;
228 boost::mutex::scoped_lock
guard(m_lock
);
229 iset_t::iterator it
= m_set
.find(id
);
230 if (it
!= m_set
.end()) {
231 remove_from_disk
= it
->remove_from_disk();
232 erase_element(&(*it
));
238 if (remove_from_disk
) {
241 dnet_setup_id(&raw
, 0, (unsigned char *)id
);
244 dnet_remove_local(m_node
, &raw
);
252 struct dnet_node
*m_node
;
253 size_t m_cache_size
, m_max_cache_size
;
257 life_set_t m_lifeset
;
258 boost::thread m_lifecheck
;
260 void resize(size_t reserve
) {
261 while (!m_lru
.empty()) {
262 data_t
*raw
= &m_lru
.front();
267 /* break early if free space in cache more than requested reserve */
268 if (m_max_cache_size
- m_cache_size
> reserve
)
273 void erase_element(data_t
*obj
) {
274 m_lru
.erase(m_lru
.iterator_to(*obj
));
275 m_set
.erase(m_set
.iterator_to(*obj
));
277 m_lifeset
.erase(m_lifeset
.iterator_to(*obj
));
279 m_cache_size
-= obj
->size();
284 void life_check(void) {
285 while (!m_need_exit
) {
286 std::deque
<struct dnet_id
> remove
;
288 while (!m_need_exit
&& !m_lifeset
.empty()) {
289 size_t time
= ::time(NULL
);
291 boost::mutex::scoped_lock
guard(m_lock
);
293 if (m_lifeset
.empty())
296 life_set_t::iterator it
= m_lifeset
.begin();
297 if (it
->lifetime() > time
)
300 if (it
->remove_from_disk()) {
303 dnet_setup_id(&id
, 0, (unsigned char *)it
->id().id
);
306 remove
.push_back(id
);
309 erase_element(&(*it
));
312 for (std::deque
<struct dnet_id
>::iterator it
= remove
.begin(); it
!= remove
.end(); ++it
) {
313 dnet_remove_local(m_node
, &(*it
));
323 using namespace ioremap::cache
;
325 int dnet_cmd_cache_io(struct dnet_net_state
*st
, struct dnet_cmd
*cmd
, struct dnet_io_attr
*io
, char *data
)
327 struct dnet_node
*n
= st
->n
;
333 cache_t
*cache
= (cache_t
*)n
->cache
;
336 boost::shared_ptr
<raw_data_t
> d
;
340 if (io
->flags
& DNET_IO_FLAGS_COMPARE_AND_SWAP
) {
341 d
= cache
->read(io
->id
);
344 dnet_transform(n
, d
->data().data(), d
->data().size(), &csum
);
346 if (!memcmp(csum
.id
, io
->parent
, DNET_ID_SIZE
)) {
352 cache
->write(io
->id
, io
->start
, data
, io
->size
, !!(io
->flags
& DNET_IO_FLAGS_CACHE_REMOVE_FROM_DISK
));
356 d
= cache
->read(io
->id
);
357 if (io
->offset
+ io
->size
> d
->size()) {
358 dnet_log_raw(n
, DNET_LOG_ERROR
, "%s: %s cache: invalid offset/size: "
359 "offset: %llu, size: %llu, cached-size: %zd\n",
360 dnet_dump_id(&cmd
->id
), dnet_cmd_string(cmd
->cmd
),
361 (unsigned long long)io
->offset
, (unsigned long long)io
->size
,
367 io
->size
= d
->size();
368 err
= dnet_send_read_data(st
, cmd
, io
, (char *)d
->data().data() + io
->offset
, -1, io
->offset
, 0);
372 if (cache
->remove(cmd
->id
.id
))
376 } catch (const std::exception
&e
) {
377 dnet_log_raw(n
, DNET_LOG_ERROR
, "%s: %s cache operation failed: %s\n",
378 dnet_dump_id(&cmd
->id
), dnet_cmd_string(cmd
->cmd
), e
.what());
385 int dnet_cache_init(struct dnet_node
*n
)
391 n
->cache
= (void *)(new cache_t(n
));
392 } catch (const std::exception
&e
) {
393 dnet_log_raw(n
, DNET_LOG_ERROR
, "Could not create cache: %s\n", e
.what());
400 void dnet_cache_cleanup(struct dnet_node
*n
)
403 delete (cache_t
*)n
->cache
;