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.
19 #include <boost/unordered_map.hpp>
20 #include <boost/shared_array.hpp>
21 #include <boost/shared_ptr.hpp>
22 #include <boost/make_shared.hpp>
23 #include <boost/thread.hpp>
24 #include <boost/intrusive/list.hpp>
25 #include <boost/intrusive/set.hpp>
27 #include "../library/elliptics.h"
29 #include "elliptics/packet.h"
30 #include "elliptics/interface.h"
32 namespace ioremap
{ namespace cache
{
35 key_t(const unsigned char *id
) {
36 memcpy(this->id
, id
, DNET_ID_SIZE
);
39 unsigned char id
[DNET_ID_SIZE
];
42 size_t hash(const unsigned char *id
) {
43 size_t num
= DNET_ID_SIZE
/ sizeof(size_t);
45 size_t *ptr
= (size_t *)id
;
46 size_t hash
= 0x883eaf5a;
47 for (size_t i
= 0; i
< num
; ++i
)
54 std::size_t operator()(const key_t
&key
) const {
55 return ioremap::cache::hash(key
.id
);
60 bool operator() (const key_t
&x
, const key_t
&y
) const {
61 return memcmp(x
.id
, y
.id
, DNET_ID_SIZE
) == 0;
67 raw_data_t(const char *data
, size_t size
) {
69 m_data
.insert(m_data
.begin(), data
, data
+ size
);
72 std::vector
<char> &data(void) {
81 std::vector
<char> m_data
;
84 struct data_lru_tag_t
;
85 typedef boost::intrusive::list_base_hook
<boost::intrusive::tag
<data_lru_tag_t
>,
86 boost::intrusive::link_mode
<boost::intrusive::safe_link
>
87 > lru_list_base_hook_t
;
88 struct data_set_tag_t
;
89 typedef boost::intrusive::set_base_hook
<boost::intrusive::tag
<data_set_tag_t
>,
90 boost::intrusive::link_mode
<boost::intrusive::safe_link
>
93 struct time_set_tag_t
;
94 typedef boost::intrusive::set_base_hook
<boost::intrusive::tag
<time_set_tag_t
>,
95 boost::intrusive::link_mode
<boost::intrusive::safe_link
>
96 > time_set_base_hook_t
;
98 class data_t
: public lru_list_base_hook_t
, public set_base_hook_t
, public time_set_base_hook_t
{
100 data_t(const unsigned char *id
) : m_lifetime(0) {
101 memcpy(m_id
.id
, id
, DNET_ID_SIZE
);
104 data_t(const unsigned char *id
, size_t lifetime
, const char *data
, size_t size
, bool remove_from_disk
) :
105 m_lifetime(0), m_remove_from_disk(remove_from_disk
) {
106 memcpy(m_id
.id
, id
, DNET_ID_SIZE
);
109 m_lifetime
= lifetime
+ time(NULL
);
111 m_data
.reset(new raw_data_t(data
, size
));
117 const struct dnet_raw_id
&id(void) const {
121 boost::shared_ptr
<raw_data_t
> data(void) const {
125 size_t lifetime(void) const {
129 bool remove_from_disk() const {
130 return m_remove_from_disk
;
133 size_t size(void) const {
134 return m_data
->size();
137 friend bool operator< (const data_t
&a
, const data_t
&b
) {
138 return dnet_id_cmp_str(a
.id().id
, b
.id().id
) < 0;
141 friend bool operator> (const data_t
&a
, const data_t
&b
) {
142 return dnet_id_cmp_str(a
.id().id
, b
.id().id
) > 0;
145 friend bool operator== (const data_t
&a
, const data_t
&b
) {
146 return dnet_id_cmp_str(a
.id().id
, b
.id().id
) == 0;
151 bool m_remove_from_disk
;
152 struct dnet_raw_id m_id
;
153 boost::shared_ptr
<raw_data_t
> m_data
;
156 typedef boost::intrusive::list
<data_t
, boost::intrusive::base_hook
<lru_list_base_hook_t
> > lru_list_t
;
157 typedef boost::intrusive::set
<data_t
, boost::intrusive::base_hook
<set_base_hook_t
>,
158 boost::intrusive::compare
<std::less
<data_t
> >
161 struct lifetime_less
{
162 bool operator() (const data_t
&x
, const data_t
&y
) const {
163 return x
.lifetime() < y
.lifetime();
167 typedef boost::intrusive::set
<data_t
, boost::intrusive::base_hook
<time_set_base_hook_t
>,
168 boost::intrusive::compare
<lifetime_less
>
173 cache_t(struct dnet_node
*n
) : m_need_exit(false), m_node(n
), m_cache_size(0), m_max_cache_size(n
->cache_size
) {
174 m_lifecheck
= boost::thread(boost::bind(&cache_t::life_check
, this));
181 while (!m_lru
.empty()) {
182 data_t raw
= m_lru
.front();
187 void write(const unsigned char *id
, size_t lifetime
, const char *data
, size_t size
, bool remove_from_disk
) {
188 boost::mutex::scoped_lock
guard(m_lock
);
190 iset_t::iterator it
= m_set
.find(id
);
191 if (it
!= m_set
.end())
192 erase_element(&(*it
));
194 if (size
+ m_cache_size
> m_max_cache_size
)
198 * nothing throws exception below this 'new' operator, so there is no try/catch block
200 data_t
*raw
= new data_t(id
, lifetime
, data
, size
, remove_from_disk
);
203 m_lru
.push_back(*raw
);
205 m_lifeset
.insert(*raw
);
207 m_cache_size
+= size
;
210 boost::shared_ptr
<raw_data_t
> read(const unsigned char *id
) {
211 boost::mutex::scoped_lock
guard(m_lock
);
213 iset_t::iterator it
= m_set
.find(id
);
214 if (it
== m_set
.end())
215 throw std::runtime_error("no record");
217 m_lru
.erase(m_lru
.iterator_to(*it
));
218 m_lru
.push_back(*it
);
222 bool remove(const unsigned char *id
) {
223 bool removed
= false;
224 bool remove_from_disk
= false;
226 boost::mutex::scoped_lock
guard(m_lock
);
227 iset_t::iterator it
= m_set
.find(id
);
228 if (it
!= m_set
.end()) {
229 remove_from_disk
= it
->remove_from_disk();
230 erase_element(&(*it
));
236 if (remove_from_disk
) {
239 dnet_setup_id(&raw
, 0, (unsigned char *)id
);
242 dnet_remove_local(m_node
, &raw
);
250 struct dnet_node
*m_node
;
251 size_t m_cache_size
, m_max_cache_size
;
255 life_set_t m_lifeset
;
256 boost::thread m_lifecheck
;
258 void resize(size_t reserve
) {
259 while (!m_lru
.empty()) {
260 data_t
*raw
= &m_lru
.front();
265 /* break early if free space in cache more than requested reserve */
266 if (m_max_cache_size
- m_cache_size
> reserve
)
271 void erase_element(data_t
*obj
) {
272 m_lru
.erase(m_lru
.iterator_to(*obj
));
273 m_set
.erase(m_set
.iterator_to(*obj
));
275 m_lifeset
.erase(m_lifeset
.iterator_to(*obj
));
277 m_cache_size
-= obj
->size();
282 void life_check(void) {
283 while (!m_need_exit
) {
284 std::deque
<struct dnet_id
> remove
;
286 while (!m_need_exit
&& !m_lifeset
.empty()) {
287 size_t time
= ::time(NULL
);
289 boost::mutex::scoped_lock
guard(m_lock
);
291 if (m_lifeset
.empty())
294 life_set_t::iterator it
= m_lifeset
.begin();
295 if (it
->lifetime() > time
)
298 if (it
->remove_from_disk()) {
301 dnet_setup_id(&id
, 0, (unsigned char *)it
->id().id
);
304 remove
.push_back(id
);
307 erase_element(&(*it
));
310 for (std::deque
<struct dnet_id
>::iterator it
= remove
.begin(); it
!= remove
.end(); ++it
) {
311 dnet_remove_local(m_node
, &(*it
));
321 using namespace ioremap::cache
;
323 int dnet_cmd_cache_io(struct dnet_net_state
*st
, struct dnet_cmd
*cmd
, struct dnet_io_attr
*io
, char *data
)
325 struct dnet_node
*n
= st
->n
;
331 cache_t
*cache
= (cache_t
*)n
->cache
;
334 boost::shared_ptr
<raw_data_t
> d
;
338 cache
->write(io
->id
, io
->start
, data
, io
->size
, !!(io
->flags
& DNET_IO_FLAGS_CACHE_REMOVE_FROM_DISK
));
342 d
= cache
->read(io
->id
);
343 if (io
->offset
+ io
->size
> d
->size()) {
344 dnet_log_raw(n
, DNET_LOG_ERROR
, "%s: %s cache: invalid offset/size: "
345 "offset: %llu, size: %llu, cached-size: %zd\n",
346 dnet_dump_id(&cmd
->id
), dnet_cmd_string(cmd
->cmd
),
347 (unsigned long long)io
->offset
, (unsigned long long)io
->size
,
353 io
->size
= d
->size();
354 err
= dnet_send_read_data(st
, cmd
, io
, (char *)d
->data().data() + io
->offset
, -1, io
->offset
, 0);
358 if (cache
->remove(cmd
->id
.id
))
362 } catch (const std::exception
&e
) {
363 dnet_log_raw(n
, DNET_LOG_ERROR
, "%s: %s cache operation failed: %s\n",
364 dnet_dump_id(&cmd
->id
), dnet_cmd_string(cmd
->cmd
), e
.what());
371 int dnet_cache_init(struct dnet_node
*n
)
377 n
->cache
= (void *)(new cache_t(n
));
378 } catch (const std::exception
&e
) {
379 dnet_log_raw(n
, DNET_LOG_ERROR
, "Could not create cache: %s\n", e
.what());
386 void dnet_cache_cleanup(struct dnet_node
*n
)
389 delete (cache_t
*)n
->cache
;