--- /dev/null
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+/*
+ * Ceph - scalable distributed file system
+ *
+ * Copyright (C) 2013 UnitedStack <haomai@unitedstack.com>
+ *
+ * Author: Haomai Wang <haomaiwang@gmail.com>
+ *
+ * This is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License version 2.1, as published by the Free Software
+ * Foundation. See file COPYING.
+ *
+ */
+
+#include "include/int_types.h"
+#include "include/buffer.h"
+
+#include <iostream>
+#include <set>
+#include <map>
+#include <string>
+#include <vector>
+
+#include <errno.h>
+
+#include "GenericObjectMap.h"
+#include "common/debug.h"
+#include "common/config.h"
+#include "include/assert.h"
+
+#define dout_subsys ceph_subsys_keyvaluestore
+const string GenericObjectMap::GLOBAL_STATE_KEY = "HEADER";
+
+const string GenericObjectMap::USER_PREFIX = "_SEQ_";
+const string GenericObjectMap::INTERN_PREFIX = "_INTERN_";
+const string GenericObjectMap::COMPLETE_PREFIX = "_COMPLETE_";
+const string GenericObjectMap::GHOBJECT_TO_SEQ_PREFIX = "_GHOBJTOSEQ_";
+const string GenericObjectMap::PARENT_KEY = "_PARENT_HEADER_";
+
+// In order to make right ordering for leveldb matching with hobject_t,
+// so use "!" to separated
+const string GenericObjectMap::GHOBJECT_KEY_SEP_S = "!";
+const char GenericObjectMap::GHOBJECT_KEY_SEP_C = '!';
+
+// ============== GenericObjectMap Key Function =================
+
+static void append_escaped(const string &in, string *out)
+{
+ for (string::const_iterator i = in.begin(); i != in.end(); ++i) {
+ if (*i == '%') {
+ out->push_back('%');
+ out->push_back('p');
+ } else if (*i == '.') {
+ out->push_back('%');
+ out->push_back('e');
+ } else if (*i == GenericObjectMap::GHOBJECT_KEY_SEP_C) {
+ out->push_back('%');
+ out->push_back('u');
+ } else if (*i == '!') {
+ out->push_back('%');
+ out->push_back('s');
+ } else {
+ out->push_back(*i);
+ }
+ }
+}
+
+static bool append_unescaped(string::const_iterator begin,
+ string::const_iterator end,
+ string *out)
+{
+ for (string::const_iterator i = begin; i != end; ++i) {
+ if (*i == '%') {
+ ++i;
+ if (*i == 'p')
+ out->push_back('%');
+ else if (*i == 'e')
+ out->push_back('.');
+ else if (*i == 'u')
+ out->push_back(GenericObjectMap::GHOBJECT_KEY_SEP_C);
+ else if (*i == 's')
+ out->push_back('!');
+ else
+ return false;
+ } else {
+ out->push_back(*i);
+ }
+ }
+ return true;
+}
+
+string GenericObjectMap::header_key(const coll_t &cid)
+{
+ string full_name;
+
+ append_escaped(cid.to_str(), &full_name);
+ full_name.append(GHOBJECT_KEY_SEP_S);
+ return full_name;
+}
+
+string GenericObjectMap::header_key(const coll_t &cid, const ghobject_t &oid)
+{
+ string full_name;
+
+ append_escaped(cid.to_str(), &full_name);
+ full_name.append(GHOBJECT_KEY_SEP_S);
+
+ char buf[PATH_MAX];
+ char *t = buf;
+ char *end = t + sizeof(buf);
+
+ // make field ordering match with hobject_t compare operations
+ snprintf(t, end - t, "%.*X", (int)(sizeof(oid.hobj.hash)*2),
+ (uint32_t)oid.get_filestore_key_u32());
+ full_name += string(buf);
+ full_name.append(GHOBJECT_KEY_SEP_S);
+
+ append_escaped(oid.hobj.nspace, &full_name);
+ full_name.append(GHOBJECT_KEY_SEP_S);
+
+ t = buf;
+ if (oid.hobj.pool == -1)
+ t += snprintf(t, end - t, "none");
+ else
+ t += snprintf(t, end - t, "%llx", (long long unsigned)oid.hobj.pool);
+ full_name += string(buf);
+ full_name.append(GHOBJECT_KEY_SEP_S);
+
+ append_escaped(oid.hobj.get_key(), &full_name);
+ full_name.append(GHOBJECT_KEY_SEP_S);
+
+ append_escaped(oid.hobj.oid.name, &full_name);
+ full_name.append(GHOBJECT_KEY_SEP_S);
+
+ t = buf;
+ if (oid.hobj.snap == CEPH_NOSNAP)
+ t += snprintf(t, end - t, "head");
+ else if (oid.hobj.snap == CEPH_SNAPDIR)
+ t += snprintf(t, end - t, "snapdir");
+ else
+ // Keep length align
+ t += snprintf(t, end - t, "%016llx", (long long unsigned)oid.hobj.snap);
+ full_name += string(buf);
+
+ if (oid.generation != ghobject_t::NO_GEN) {
+ assert(oid.shard_id != ghobject_t::NO_SHARD);
+ full_name.append(GHOBJECT_KEY_SEP_S);
+
+ t = buf;
+ end = t + sizeof(buf);
+ t += snprintf(t, end - t, "%llx", (long long unsigned)oid.generation);
+ full_name += string(buf);
+
+ full_name.append(GHOBJECT_KEY_SEP_S);
+
+ t = buf;
+ end = t + sizeof(buf);
+ t += snprintf(t, end - t, "%x", (int)oid.shard_id);
+ full_name += string(buf);
+ }
+
+ return full_name;
+}
+
+bool GenericObjectMap::parse_header_key(const string &long_name,
+ coll_t *out_coll, ghobject_t *out)
+{
+ string coll;
+ string name;
+ string key;
+ string ns;
+ uint32_t hash;
+ snapid_t snap;
+ uint64_t pool;
+ gen_t generation = ghobject_t::NO_GEN;
+ shard_t shard_id = ghobject_t::NO_SHARD;
+
+ string::const_iterator current = long_name.begin();
+ string::const_iterator end;
+
+ for (end = current; end != long_name.end() && *end != GHOBJECT_KEY_SEP_C; ++end) ;
+ if (!append_unescaped(current, end, &coll))
+ return false;
+
+ current = ++end;
+ for ( ; end != long_name.end() && *end != GHOBJECT_KEY_SEP_C; ++end) ;
+ if (end == long_name.end())
+ return false;
+ string hash_str(current, end);
+ sscanf(hash_str.c_str(), "%X", &hash);
+
+ current = ++end;
+ for ( ; end != long_name.end() && *end != GHOBJECT_KEY_SEP_C; ++end) ;
+ if (end == long_name.end())
+ return false;
+ if (!append_unescaped(current, end, &ns))
+ return false;
+
+ current = ++end;
+ for ( ; end != long_name.end() && *end != GHOBJECT_KEY_SEP_C; ++end) ;
+ if (end == long_name.end())
+ return false;
+ string pstring(current, end);
+ if (pstring == "none")
+ pool = (uint64_t)-1;
+ else
+ pool = strtoull(pstring.c_str(), NULL, 16);
+
+ current = ++end;
+ for ( ; end != long_name.end() && *end != GHOBJECT_KEY_SEP_C; ++end) ;
+ if (end == long_name.end())
+ return false;
+ if (!append_unescaped(current, end, &key))
+ return false;
+
+ current = ++end;
+ for ( ; end != long_name.end() && *end != GHOBJECT_KEY_SEP_C; ++end) ;
+ if (end == long_name.end())
+ return false;
+ if (!append_unescaped(current, end, &name))
+ return false;
+
+ current = ++end;
+ for ( ; end != long_name.end() && *end != GHOBJECT_KEY_SEP_C; ++end) ;
+ string snap_str(current, end);
+ if (snap_str == "head")
+ snap = CEPH_NOSNAP;
+ else if (snap_str == "snapdir")
+ snap = CEPH_SNAPDIR;
+ else
+ snap = strtoull(snap_str.c_str(), NULL, 16);
+
+ // Optional generation/shard_id
+ string genstring, shardstring;
+ if (end != long_name.end()) {
+ current = ++end;
+ for ( ; end != long_name.end() && *end != GHOBJECT_KEY_SEP_C; ++end) ;
+ if (end == long_name.end())
+ return false;
+ genstring = string(current, end);
+
+ generation = (gen_t)strtoull(genstring.c_str(), NULL, 16);
+
+ current = ++end;
+ for ( ; end != long_name.end() && *end != GHOBJECT_KEY_SEP_C; ++end) ;
+ if (end != long_name.end())
+ return false;
+ shardstring = string(current, end);
+
+ shard_id = (shard_t)strtoul(shardstring.c_str(), NULL, 16);
+ }
+
+ if (out) {
+ (*out) = ghobject_t(hobject_t(name, key, snap, hash, (int64_t)pool, ns),
+ generation, shard_id);
+ // restore reversed hash. see calculate_key
+ out->hobj.hash = out->get_filestore_key();
+ }
+
+ if (out_coll)
+ *out_coll = coll_t(coll);
+
+ return true;
+}
+
+
+// ============== GenericObjectMap Prefix =================
+
+string GenericObjectMap::user_prefix(Header header, const string &prefix)
+{
+ return USER_PREFIX + seq_key(header->seq) + prefix;
+}
+
+string GenericObjectMap::complete_prefix(Header header)
+{
+ return INTERN_PREFIX + seq_key(header->seq) + COMPLETE_PREFIX;
+}
+
+string GenericObjectMap::parent_seq_prefix(uint64_t seq)
+{
+ return INTERN_PREFIX + seq_key(seq) + PARENT_KEY;
+}
+
+
+// ============== GenericObjectMapIteratorImpl =================
+
+int GenericObjectMap::GenericObjectMapIteratorImpl::init()
+{
+ invalid = false;
+ if (ready) {
+ return 0;
+ }
+
+ assert(!parent_iter);
+ if (header->parent) {
+ Header parent = map->lookup_parent(header);
+ if (!parent) {
+ assert(0);
+ return -EINVAL;
+ }
+ parent_iter.reset(new GenericObjectMapIteratorImpl(map, parent, prefix));
+ }
+
+ key_iter = map->db->get_iterator(map->user_prefix(header, prefix));
+ assert(key_iter);
+ complete_iter = map->db->get_iterator(map->complete_prefix(header));
+ assert(complete_iter);
+ cur_iter = key_iter;
+ assert(cur_iter);
+ ready = true;
+ return 0;
+}
+
+ObjectMap::ObjectMapIterator GenericObjectMap::get_iterator(
+ const coll_t &cid, const ghobject_t &oid, const string &prefix)
+{
+ Header header = lookup_header(cid, oid);
+ if (!header)
+ return ObjectMap::ObjectMapIterator(new EmptyIteratorImpl());
+ return _get_iterator(header, prefix);
+}
+
+int GenericObjectMap::GenericObjectMapIteratorImpl::seek_to_first()
+{
+ init();
+ r = 0;
+ if (parent_iter) {
+ r = parent_iter->seek_to_first();
+ if (r < 0)
+ return r;
+ }
+ r = key_iter->seek_to_first();
+ if (r < 0)
+ return r;
+ return adjust();
+}
+
+int GenericObjectMap::GenericObjectMapIteratorImpl::seek_to_last()
+{
+ init();
+ r = 0;
+ if (parent_iter) {
+ r = parent_iter->seek_to_last();
+ if (r < 0)
+ return r;
+ if (parent_iter->valid())
+ r = parent_iter->next();
+ if (r < 0)
+ return r;
+ }
+ r = key_iter->seek_to_last();
+ if (r < 0)
+ return r;
+ if (key_iter->valid())
+ r = key_iter->next();
+ if (r < 0)
+ return r;
+ return adjust();
+}
+
+int GenericObjectMap::GenericObjectMapIteratorImpl::lower_bound(const string &to)
+{
+ init();
+ r = 0;
+ if (parent_iter) {
+ r = parent_iter->lower_bound(to);
+ if (r < 0)
+ return r;
+ }
+ r = key_iter->lower_bound(to);
+ if (r < 0)
+ return r;
+ return adjust();
+}
+
+int GenericObjectMap::GenericObjectMapIteratorImpl::upper_bound(const string &after)
+{
+ init();
+ r = 0;
+ if (parent_iter) {
+ r = parent_iter->upper_bound(after);
+ if (r < 0)
+ return r;
+ }
+ r = key_iter->upper_bound(after);
+ if (r < 0)
+ return r;
+ return adjust();
+}
+
+bool GenericObjectMap::GenericObjectMapIteratorImpl::valid()
+{
+ bool valid = !invalid && ready;
+ assert(!valid || cur_iter->valid());
+ return valid;
+}
+
+bool GenericObjectMap::GenericObjectMapIteratorImpl::valid_parent()
+{
+ if (parent_iter && parent_iter->valid() &&
+ (!key_iter->valid() || key_iter->key() > parent_iter->key()))
+ return true;
+ return false;
+}
+
+int GenericObjectMap::GenericObjectMapIteratorImpl::next()
+{
+ assert(cur_iter->valid());
+ assert(valid());
+ cur_iter->next();
+ return adjust();
+}
+
+int GenericObjectMap::GenericObjectMapIteratorImpl::next_parent()
+{
+ if (!parent_iter || !parent_iter->valid()) {
+ invalid = true;
+ return 0;
+ }
+ r = next();
+ if (r < 0)
+ return r;
+ if (!valid() || on_parent() || !parent_iter->valid())
+ return 0;
+
+ return lower_bound(parent_iter->key());
+}
+
+int GenericObjectMap::GenericObjectMapIteratorImpl::in_complete_region(
+ const string &to_test, string *begin, string *end)
+{
+ complete_iter->upper_bound(to_test);
+ if (complete_iter->valid())
+ complete_iter->prev();
+ else
+ complete_iter->seek_to_last();
+
+ if (!complete_iter->valid())
+ return false;
+
+ string _end;
+ if (begin)
+ *begin = complete_iter->key();
+ _end = string(complete_iter->value().c_str());
+ if (end)
+ *end = _end;
+ return (to_test >= complete_iter->key()) && (!_end.size() || _end > to_test);
+}
+
+/**
+ * Moves parent_iter to the next position both out of the complete_region and
+ * not equal to key_iter. Then, we set cur_iter to parent_iter if valid and
+ * less than key_iter and key_iter otherwise.
+ */
+int GenericObjectMap::GenericObjectMapIteratorImpl::adjust()
+{
+ string begin, end;
+ while (parent_iter && parent_iter->valid()) {
+ if (in_complete_region(parent_iter->key(), &begin, &end)) {
+ if (end.size() == 0) {
+ parent_iter->seek_to_last();
+ if (parent_iter->valid())
+ parent_iter->next();
+ } else {
+ parent_iter->lower_bound(end);
+ }
+ } else if (key_iter->valid() && key_iter->key() == parent_iter->key()) {
+ parent_iter->next();
+ } else {
+ break;
+ }
+ }
+ if (valid_parent()) {
+ cur_iter = parent_iter;
+ } else if (key_iter->valid()) {
+ cur_iter = key_iter;
+ } else {
+ invalid = true;
+ }
+ assert(invalid || cur_iter->valid());
+ return 0;
+}
+
+string GenericObjectMap::GenericObjectMapIteratorImpl::key()
+{
+ return cur_iter->key();
+}
+
+bufferlist GenericObjectMap::GenericObjectMapIteratorImpl::value()
+{
+ return cur_iter->value();
+}
+
+int GenericObjectMap::GenericObjectMapIteratorImpl::status()
+{
+ return r;
+}
+
+
+// ============== GenericObjectMap Public API =================
+
+void GenericObjectMap::set_keys(const Header header,
+ const string &prefix,
+ const map<string, bufferlist> &set,
+ KeyValueDB::Transaction t)
+{
+ t->set(user_prefix(header, prefix), set);
+}
+
+int GenericObjectMap::clear(const Header header,
+ KeyValueDB::Transaction t)
+{
+ remove_header(header->cid, header->oid, header, t);
+ assert(header->num_children > 0);
+ header->num_children--;
+ int r = _clear(header, t);
+ if (r < 0)
+ return r;
+ return 0;
+}
+
+int GenericObjectMap::rm_keys(const Header header,
+ const string &prefix,
+ const set<string> &to_clear,
+ KeyValueDB::Transaction t)
+{
+ t->rmkeys(user_prefix(header, prefix), to_clear);
+ if (!header->parent) {
+ return 0;
+ }
+
+ // Copy up keys from parent around to_clear
+ int keep_parent;
+ {
+ GenericObjectMapIterator iter = _get_iterator(header, prefix);
+ iter->seek_to_first();
+ map<string, string> new_complete;
+ map<string, bufferlist> to_write;
+ for(set<string>::const_iterator i = to_clear.begin();
+ i != to_clear.end(); ) {
+ unsigned copied = 0;
+ iter->lower_bound(*i);
+ ++i;
+ if (!iter->valid())
+ break;
+ string begin = iter->key();
+ if (!iter->on_parent())
+ iter->next_parent();
+ if (new_complete.size() && new_complete.rbegin()->second == begin) {
+ begin = new_complete.rbegin()->first;
+ }
+ while (iter->valid() && copied < 20) {
+ if (!to_clear.count(iter->key()))
+ to_write[iter->key()].append(iter->value());
+ if (i != to_clear.end() && *i <= iter->key()) {
+ ++i;
+ copied = 0;
+ }
+
+ iter->next_parent();
+ copied++;
+ }
+ if (iter->valid()) {
+ new_complete[begin] = iter->key();
+ } else {
+ new_complete[begin] = "";
+ break;
+ }
+ }
+ t->set(user_prefix(header, prefix), to_write);
+ merge_new_complete(header, new_complete, iter, t);
+ keep_parent = need_parent(iter);
+ if (keep_parent < 0)
+ return keep_parent;
+ }
+
+ if (!keep_parent) {
+ Header parent = lookup_parent(header);
+ if (!parent)
+ return -EINVAL;
+ parent->num_children--;
+ _clear(parent, t);
+ header->parent = 0;
+ set_header(header->cid, header->oid, *header, t);
+ t->rmkeys_by_prefix(complete_prefix(header));
+ }
+
+ return 0;
+}
+
+int GenericObjectMap::get(const coll_t &cid, const ghobject_t &oid,
+ const string &prefix,
+ map<string, bufferlist> *out)
+{
+ Header header = lookup_header(cid, oid);
+ if (!header)
+ return -ENOENT;
+
+ ObjectMap::ObjectMapIterator iter = _get_iterator(header, prefix);
+ for (iter->seek_to_first(); iter->valid(); iter->next()) {
+ if (iter->status())
+ return iter->status();
+ out->insert(make_pair(iter->key(), iter->value()));
+ }
+
+ return 0;
+}
+
+int GenericObjectMap::get_keys(const coll_t &cid, const ghobject_t &oid,
+ const string &prefix,
+ set<string> *keys)
+{
+ Header header = lookup_header(cid, oid);
+ if (!header)
+ return -ENOENT;
+
+ ObjectMap::ObjectMapIterator iter = _get_iterator(header, prefix);
+ for (; iter->valid(); iter->next()) {
+ if (iter->status())
+ return iter->status();
+ keys->insert(iter->key());
+ }
+ return 0;
+}
+int GenericObjectMap::get_values(const coll_t &cid, const ghobject_t &oid,
+ const string &prefix,
+ const set<string> &keys,
+ map<string, bufferlist> *out)
+{
+ Header header = lookup_header(cid, oid);
+ if (!header)
+ return -ENOENT;
+ return scan(header, prefix, keys, 0, out);
+}
+
+int GenericObjectMap::check_keys(const coll_t &cid, const ghobject_t &oid,
+ const string &prefix,
+ const set<string> &keys,
+ set<string> *out)
+{
+ Header header = lookup_header(cid, oid);
+ if (!header)
+ return -ENOENT;
+ return scan(header, prefix, keys, out, 0);
+}
+
+void GenericObjectMap::clone(const Header parent, const coll_t &cid,
+ const ghobject_t &target,
+ KeyValueDB::Transaction t,
+ Header *old_header, Header *new_header)
+{
+ {
+ Header destination = lookup_header(cid, target);
+ if (destination) {
+ remove_header(cid, target, destination, t);
+ destination->num_children--;
+ _clear(destination, t);
+ }
+ }
+
+ Header source = generate_new_header(parent->cid, parent->oid, parent, t);
+ Header destination = generate_new_header(cid, target, parent, t);
+
+ destination->data = parent->data;
+ source->data = parent->data;
+
+ parent->num_children = 2;
+ set_parent_header(parent, t);
+ set_header(parent->cid, parent->oid, *source, t);
+ set_header(cid, target, *destination, t);
+
+ if (new_header)
+ *old_header = source;
+ if (new_header)
+ *new_header = destination;
+}
+
+void GenericObjectMap::rename(const Header old_header, const coll_t &cid,
+ const ghobject_t &target,
+ KeyValueDB::Transaction t)
+{
+ if (old_header->oid == target && old_header->cid == cid)
+ return ;
+
+ remove_header(old_header->cid, old_header->oid, old_header, t);
+ old_header->cid = cid;
+ old_header->oid = target;
+ set_header(cid, target, *old_header, t);
+
+ // "in_use" still hold the "seq"
+}
+
+int GenericObjectMap::init(bool do_upgrade)
+{
+ map<string, bufferlist> result;
+ set<string> to_get;
+ to_get.insert(GLOBAL_STATE_KEY);
+ int r = db->get(INTERN_PREFIX, to_get, &result);
+ if (r < 0)
+ return r;
+ if (!result.empty()) {
+ bufferlist::iterator bliter = result.begin()->second.begin();
+ state.decode(bliter);
+ if (state.v < 1) { // Needs upgrade
+ if (!do_upgrade) {
+ dout(1) << "GenericObjbectMap requires an upgrade,"
+ << " set filestore_update_to"
+ << dendl;
+ return -ENOTSUP;
+ } else {
+ r = upgrade();
+ if (r < 0)
+ return r;
+ }
+ }
+ } else {
+ // New store
+ state.v = 1;
+ state.seq = 1;
+ }
+ dout(20) << "(init)genericobjectmap: seq is " << state.seq << dendl;
+ return 0;
+}
+
+int GenericObjectMap::sync(const Header header, KeyValueDB::Transaction t)
+{
+ write_state(t);
+ if (header) {
+ set_header(header->cid, header->oid, *header, t);
+ }
+ return 0;
+}
+
+bool GenericObjectMap::check(std::ostream &out)
+{
+ bool retval = true;
+ map<uint64_t, uint64_t> parent_to_num_children;
+ map<uint64_t, uint64_t> parent_to_actual_num_children;
+ KeyValueDB::Iterator iter = db->get_iterator(GHOBJECT_TO_SEQ_PREFIX);
+
+ for (iter->seek_to_first(); iter->valid(); iter->next()) {
+ _Header header;
+ assert(header.num_children == 1);
+ header.num_children = 0; // Hack for leaf node
+ bufferlist bl = iter->value();
+ while (true) {
+ bufferlist::iterator bliter = bl.begin();
+ header.decode(bliter);
+ if (header.seq != 0)
+ parent_to_actual_num_children[header.seq] = header.num_children;
+ if (header.parent == 0)
+ break;
+
+ if (!parent_to_num_children.count(header.parent))
+ parent_to_num_children[header.parent] = 0;
+ parent_to_num_children[header.parent]++;
+ if (parent_to_actual_num_children.count(header.parent))
+ break;
+
+ set<string> to_get;
+ map<string, bufferlist> got;
+ to_get.insert(PARENT_KEY);
+ db->get(parent_seq_prefix(header.parent), to_get, &got);
+ if (got.empty()) {
+ out << "Missing: seq " << header.parent << std::endl;
+ retval = false;
+ break;
+ } else {
+ bl = got.begin()->second;
+ }
+ }
+ }
+
+ for (map<uint64_t, uint64_t>::iterator i = parent_to_num_children.begin();
+ i != parent_to_num_children.end();
+ parent_to_num_children.erase(i++)) {
+ if (!parent_to_actual_num_children.count(i->first))
+ continue;
+ if (parent_to_actual_num_children[i->first] != i->second) {
+ out << "Invalid: seq " << i->first << " recorded children: "
+ << parent_to_actual_num_children[i->first] << " found: "
+ << i->second << std::endl;
+ retval = false;
+ }
+ parent_to_actual_num_children.erase(i->first);
+ }
+ return retval;
+}
+
+
+// ============== GenericObjectMap Intern Implementation =================
+
+int GenericObjectMap::scan(Header header,
+ const string &prefix,
+ const set<string> &in_keys,
+ set<string> *out_keys,
+ map<string, bufferlist> *out_values)
+{
+ ObjectMap::ObjectMapIterator db_iter = _get_iterator(header, prefix);
+ for (set<string>::const_iterator key_iter = in_keys.begin();
+ key_iter != in_keys.end();
+ ++key_iter) {
+ db_iter->lower_bound(*key_iter);
+ if (db_iter->status())
+ return db_iter->status();
+
+ if (db_iter->valid() && db_iter->key() == *key_iter) {
+ if (out_keys)
+ out_keys->insert(*key_iter);
+ if (out_values)
+ out_values->insert(make_pair(db_iter->key(), db_iter->value()));
+ }
+ }
+ return 0;
+}
+
+int GenericObjectMap::_clear(Header header, KeyValueDB::Transaction t)
+{
+ while (1) {
+ if (header->num_children) {
+ set_parent_header(header, t);
+ break;
+ }
+
+ clear_header(header, t);
+ if (!header->parent)
+ break;
+
+ Header parent = lookup_parent(header);
+ if (!parent) {
+ return -EINVAL;
+ }
+ assert(parent->num_children > 0);
+ parent->num_children--;
+ header.swap(parent);
+ }
+ return 0;
+}
+
+int GenericObjectMap::merge_new_complete(
+ Header header, const map<string, string> &new_complete,
+ GenericObjectMapIterator iter, KeyValueDB::Transaction t)
+{
+ KeyValueDB::Iterator complete_iter = db->get_iterator(
+ complete_prefix(header));
+ map<string, string>::const_iterator i = new_complete.begin();
+ set<string> to_remove;
+ map<string, bufferlist> to_add;
+
+ string begin, end;
+ while (i != new_complete.end()) {
+ string new_begin = i->first;
+ string new_end = i->second;
+ int r = iter->in_complete_region(new_begin, &begin, &end);
+ if (r < 0)
+ return r;
+ if (r) {
+ to_remove.insert(begin);
+ new_begin = begin;
+ }
+ ++i;
+ while (i != new_complete.end()) {
+ if (!new_end.size() || i->first <= new_end) {
+ if (!new_end.size() && i->second > new_end) {
+ new_end = i->second;
+ }
+ ++i;
+ continue;
+ }
+
+ r = iter->in_complete_region(new_end, &begin, &end);
+ if (r < 0)
+ return r;
+ if (r) {
+ to_remove.insert(begin);
+ new_end = end;
+ continue;
+ }
+ break;
+ }
+ bufferlist bl;
+ bl.append(bufferptr(new_end.c_str(), new_end.size() + 1));
+ to_add.insert(make_pair(new_begin, bl));
+ }
+ t->rmkeys(complete_prefix(header), to_remove);
+ t->set(complete_prefix(header), to_add);
+ return 0;
+}
+
+int GenericObjectMap::need_parent(GenericObjectMapIterator iter)
+{
+ int r = iter->seek_to_first();
+ if (r < 0)
+ return r;
+
+ if (!iter->valid())
+ return 0;
+
+ string begin, end;
+ if (iter->in_complete_region(iter->key(), &begin, &end) && end == "") {
+ return 0;
+ }
+ return 1;
+}
+
+int GenericObjectMap::write_state(KeyValueDB::Transaction t)
+{
+ dout(20) << __func__ << " seq is " << state.seq << dendl;
+ bufferlist bl;
+ state.encode(bl);
+ map<string, bufferlist> to_write;
+ to_write[GLOBAL_STATE_KEY] = bl;
+ t->set(INTERN_PREFIX, to_write);
+ return 0;
+}
+
+GenericObjectMap::Header GenericObjectMap::_lookup_header(
+ const coll_t &cid, const ghobject_t &oid)
+{
+ // FIXME
+ while (map_header_in_use.count(oid))
+ header_cond.Wait(header_lock);
+
+ map<string, bufferlist> out;
+ set<string> to_get;
+ to_get.insert(header_key(cid, oid));
+ int r = db->get(GHOBJECT_TO_SEQ_PREFIX, to_get, &out);
+ if (r < 0)
+ return Header();
+ if (out.empty())
+ return Header();
+
+ Header ret(new _Header(), RemoveMapHeaderOnDelete(this, cid, oid));
+ bufferlist::iterator iter = out.begin()->second.begin();
+ ret->decode(iter);
+ return ret;
+}
+
+GenericObjectMap::Header GenericObjectMap::_generate_new_header(
+ const coll_t &cid, const ghobject_t &oid, Header parent,
+ KeyValueDB::Transaction t)
+{
+ Header header = Header(new _Header(), RemoveOnDelete(this));
+ header->seq = state.seq++;
+ if (parent) {
+ header->parent = parent->seq;
+ }
+ header->num_children = 1;
+ header->oid = oid;
+ header->cid = cid;
+ assert(!in_use.count(header->seq));
+ in_use.insert(header->seq);
+
+ write_state(t);
+ return header;
+}
+
+GenericObjectMap::Header GenericObjectMap::lookup_parent(Header input)
+{
+ Mutex::Locker l(header_lock);
+ while (in_use.count(input->parent))
+ header_cond.Wait(header_lock);
+ map<string, bufferlist> out;
+ set<string> keys;
+ keys.insert(PARENT_KEY);
+
+ dout(20) << "lookup_parent: parent " << input->parent
+ << " for seq " << input->seq << dendl;
+ int r = db->get(parent_seq_prefix(input->parent), keys, &out);
+ if (r < 0) {
+ assert(0);
+ return Header();
+ }
+ if (out.empty()) {
+ assert(0);
+ return Header();
+ }
+
+ Header header = Header(new _Header(), RemoveOnDelete(this));
+ header->seq = input->parent;
+ bufferlist::iterator iter = out.begin()->second.begin();
+ header->decode(iter);
+ dout(20) << "lookup_parent: parent seq is " << header->seq << " with parent "
+ << header->parent << dendl;
+ in_use.insert(header->seq);
+ return header;
+}
+
+GenericObjectMap::Header GenericObjectMap::lookup_create_header(
+ const coll_t &cid, const ghobject_t &oid, KeyValueDB::Transaction t)
+{
+ Mutex::Locker l(header_lock);
+ Header header = _lookup_header(cid, oid);
+ if (!header) {
+ header = _generate_new_header(cid, oid, Header(), t);
+ set_header(cid, oid, *header, t);
+ }
+ return header;
+}
+
+void GenericObjectMap::set_parent_header(Header header, KeyValueDB::Transaction t)
+{
+ dout(20) << __func__ << " setting seq " << header->seq << dendl;
+ map<string, bufferlist> to_write;
+ header->encode(to_write[PARENT_KEY]);
+ t->set(parent_seq_prefix(header->seq), to_write);
+}
+
+void GenericObjectMap::clear_header(Header header, KeyValueDB::Transaction t)
+{
+ dout(20) << __func__ << " clearing seq " << header->seq << dendl;
+ t->rmkeys_by_prefix(user_prefix(header, string()));
+ t->rmkeys_by_prefix(complete_prefix(header));
+ set<string> keys;
+ keys.insert(PARENT_KEY);
+ t->rmkeys(parent_seq_prefix(header->seq), keys);
+}
+
+void GenericObjectMap::remove_header(const coll_t &cid,
+ const ghobject_t &oid, Header header,
+ KeyValueDB::Transaction t)
+{
+ dout(20) << __func__ << " removing " << header->seq
+ << " cid " << cid << " oid " << oid << dendl;
+ set<string> to_remove;
+ to_remove.insert(header_key(cid, oid));
+ t->rmkeys(GHOBJECT_TO_SEQ_PREFIX, to_remove);
+}
+
+void GenericObjectMap::set_header(const coll_t &cid, const ghobject_t &oid,
+ _Header header, KeyValueDB::Transaction t)
+{
+ dout(20) << __func__ << " setting " << header.seq
+ << " cid " << cid << " oid " << oid << " parent seq "
+ << header.parent << dendl;
+ map<string, bufferlist> to_set;
+ header.encode(to_set[header_key(cid, oid)]);
+ t->set(GHOBJECT_TO_SEQ_PREFIX, to_set);
+}
+
+int GenericObjectMap::list_objects(const coll_t &cid, ghobject_t start, int max,
+ vector<ghobject_t> *out, ghobject_t *next)
+{
+ Mutex::Locker l(header_lock);
+
+ if (start.is_max())
+ return 0;
+
+ if (start.hobj.is_min()) {
+ vector<ghobject_t> oids;
+
+ KeyValueDB::Iterator iter = db->get_iterator(GHOBJECT_TO_SEQ_PREFIX);
+ for (iter->lower_bound(header_key(cid)); iter->valid(); iter->next()) {
+ bufferlist bl = iter->value();
+ bufferlist::iterator bliter = bl.begin();
+ _Header header;
+ header.decode(bliter);
+
+ if (header.cid == cid)
+ oids.push_back(header.oid);
+
+ break;
+ }
+
+ if (oids.empty()) {
+ if (next)
+ *next = ghobject_t::get_max();
+ return 0;
+ }
+ start = oids[0];
+ }
+
+ int size = 0;
+ KeyValueDB::Iterator iter = db->get_iterator(GHOBJECT_TO_SEQ_PREFIX);
+ for (iter->lower_bound(header_key(cid, start)); iter->valid(); iter->next()) {
+ bufferlist bl = iter->value();
+ bufferlist::iterator bliter = bl.begin();
+ _Header header;
+ header.decode(bliter);
+
+ if (header.cid != cid) {
+ if (next)
+ *next = ghobject_t::get_max();
+ break;
+ }
+
+ if (max && size >= max) {
+ if (next)
+ *next = header.oid;
+ break;
+ }
+
+ assert(start <= header.oid);
+
+
+ size++;
+ if (out)
+ out->push_back(header.oid);
+ start = header.oid;
+ }
+
+ if (out->size())
+ dout(20) << "objects: " << *out << dendl;
+
+ if (!iter->valid())
+ if (next)
+ *next = ghobject_t::get_max();
+
+ return 0;
+}
--- /dev/null
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+/*
+ * Ceph - scalable distributed file system
+ *
+ * Copyright (C) 2013 UnitedStack <haomai@unitedstack.com>
+ *
+ * Author: Haomai Wang <haomaiwang@gmail.com>
+ *
+ * This is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License version 2.1, as published by the Free Software
+ * Foundation. See file COPYING.
+ *
+ */
+
+#ifndef CEPH_GENERICOBJECTMAP_H
+#define CEPH_GENERICOBJECTMAP_H
+
+#include "include/buffer.h"
+#include <set>
+#include <map>
+#include <string>
+#include <vector>
+#include <boost/scoped_ptr.hpp>
+
+#include "include/memory.h"
+#include "ObjectMap.h"
+#include "KeyValueDB.h"
+#include "osd/osd_types.h"
+#include "common/Mutex.h"
+#include "common/Cond.h"
+
+/**
+ * Genericobjectmap: Provide with key/value associated to ghobject_t APIs to caller
+ * and avoid concerning too much. Wrap and combine KeyValueDB/ObjectMap APIs
+ * with ghobject_t and adding clone capacity.
+ *
+ * Prefix space structure:
+ *
+ * - GHOBJECT_TO_SEQ: Contains leaf mapping from ghobject_t->Header(including
+ * hobj.seq and related metadata)
+ * - INTERN_PREFIX: GLOBAL_STATE_KEY - contains the global state
+ * @see State
+ * @see write_state
+ * @see init
+ * @see generate_new_header
+ * - INTERN_PREFIX + header_key(header->seq) + COMPLETE_PREFIX: see below
+ * - INTERN_PREFIX + header_key(header->seq) + PARENT_KEY
+ * : used to store parent header(same as headers in GHOBJECT_TO_SEQ)
+ * - USER_PREFIX + header_key(header->seq) + [CUSTOM_PREFIX]
+ * : key->value which set by callers
+ *
+ * For each node (represented by a header), we
+ * store three mappings: the key mapping, the complete mapping, and the parent.
+ * The complete mapping (COMPLETE_PREFIX space) is key->key. Each x->y entry in
+ * this mapping indicates that the key mapping contains all entries on [x,y).
+ * Note, max string is represented by "", so ""->"" indicates that the parent
+ * is unnecessary (@see rm_keys). When looking up a key not contained in the
+ * the complete set, we have to check the parent if we don't find it in the
+ * key set. During rm_keys, we copy keys from the parent and update the
+ * complete set to reflect the change @see rm_keys.
+ */
+
+// This class only provide basic read capacity, suggest inherit it to
+// implement write transaction to use it. @see StripObjectMap
+class GenericObjectMap {
+ public:
+ boost::scoped_ptr<KeyValueDB> db;
+
+ /**
+ * Serializes access to next_seq as well as the in_use set
+ */
+ Mutex header_lock;
+ Cond header_cond;
+ Cond map_header_cond;
+
+ /**
+ * Set of headers currently in use
+ */
+ set<uint64_t> in_use;
+ set<ghobject_t> map_header_in_use;
+
+ GenericObjectMap(KeyValueDB *db) : db(db), header_lock("GenericObjectMap") {}
+
+ int get(
+ const coll_t &cid,
+ const ghobject_t &oid,
+ const string &prefix,
+ map<string, bufferlist> *out
+ );
+
+ int get_keys(
+ const coll_t &cid,
+ const ghobject_t &oid,
+ const string &prefix,
+ set<string> *keys
+ );
+
+ int get_values(
+ const coll_t &cid,
+ const ghobject_t &oid,
+ const string &prefix,
+ const set<string> &keys,
+ map<string, bufferlist> *out
+ );
+
+ int check_keys(
+ const coll_t &cid,
+ const ghobject_t &oid,
+ const string &prefix,
+ const set<string> &keys,
+ set<string> *out
+ );
+
+ /// Read initial state from backing store
+ int init(bool upgrade = false);
+
+ /// Upgrade store to current version
+ int upgrade() {return 0;}
+
+ /// Consistency check, debug, there must be no parallel writes
+ bool check(std::ostream &out);
+
+ /// Util, list all objects, there must be no other concurrent access
+ int list_objects(const coll_t &cid, ghobject_t start, int max,
+ vector<ghobject_t> *objs, ///< [out] objects
+ ghobject_t *next);
+
+ ObjectMap::ObjectMapIterator get_iterator(const coll_t &cid,
+ const ghobject_t &oid,
+ const string &prefix);
+
+ KeyValueDB::Transaction get_transaction() { return db->get_transaction(); }
+ int submit_transaction(KeyValueDB::Transaction t) {
+ return db->submit_transaction(t);
+ }
+
+ /// persistent state for store @see generate_header
+ struct State {
+ __u8 v;
+ uint64_t seq;
+ State() : v(0), seq(1) {}
+ State(uint64_t seq) : v(0), seq(seq) {}
+
+ void encode(bufferlist &bl) const {
+ ENCODE_START(1, 1, bl);
+ ::encode(v, bl);
+ ::encode(seq, bl);
+ ENCODE_FINISH(bl);
+ }
+
+ void decode(bufferlist::iterator &bl) {
+ DECODE_START(1, bl);
+ ::decode(v, bl);
+ ::decode(seq, bl);
+ DECODE_FINISH(bl);
+ }
+
+ void dump(Formatter *f) const {
+ f->dump_unsigned("seq", seq);
+ }
+
+ static void generate_test_instances(list<State*> &o) {
+ o.push_back(new State(0));
+ o.push_back(new State(20));
+ }
+ } state;
+
+ struct _Header {
+ uint64_t seq;
+ uint64_t parent;
+ uint64_t num_children;
+
+ coll_t cid;
+ ghobject_t oid;
+
+ // Used by successor
+ bufferlist data;
+
+ void encode(bufferlist &bl) const {
+ ENCODE_START(1, 1, bl);
+ ::encode(seq, bl);
+ ::encode(parent, bl);
+ ::encode(num_children, bl);
+ ::encode(cid, bl);
+ ::encode(oid, bl);
+ ::encode(data, bl);
+ ENCODE_FINISH(bl);
+ }
+
+ void decode(bufferlist::iterator &bl) {
+ DECODE_START(1, bl);
+ ::decode(seq, bl);
+ ::decode(parent, bl);
+ ::decode(num_children, bl);
+ ::decode(cid, bl);
+ ::decode(oid, bl);
+ ::decode(data, bl);
+ DECODE_FINISH(bl);
+ }
+
+ void dump(Formatter *f) const {
+ f->dump_unsigned("seq", seq);
+ f->dump_unsigned("parent", parent);
+ f->dump_unsigned("num_children", num_children);
+ f->dump_stream("coll") << cid;
+ f->dump_stream("oid") << oid;
+ }
+
+ _Header() : seq(0), parent(0), num_children(1) {}
+ };
+
+ typedef ceph::shared_ptr<_Header> Header;
+
+ Header lookup_header(const coll_t &cid, const ghobject_t &oid) {
+ Mutex::Locker l(header_lock);
+ return _lookup_header(cid, oid);
+ }
+
+ /// Lookup or create header for c oid
+ Header lookup_create_header(const coll_t &cid, const ghobject_t &oid,
+ KeyValueDB::Transaction t);
+
+ /// Set leaf node for c and oid to the value of header
+ void set_header(const coll_t &cid, const ghobject_t &oid, _Header header,
+ KeyValueDB::Transaction t);
+
+ // Move all modify member function to "protect", in order to indicate these
+ // should be made use of by sub-class
+ void set_keys(
+ const Header header,
+ const string &prefix,
+ const map<string, bufferlist> &set,
+ KeyValueDB::Transaction t
+ );
+
+ int clear(
+ const Header header,
+ KeyValueDB::Transaction t
+ );
+
+ int rm_keys(
+ const Header header,
+ const string &prefix,
+ const set<string> &to_clear,
+ KeyValueDB::Transaction t
+ );
+
+ void clone(
+ const Header origin_header,
+ const coll_t &cid,
+ const ghobject_t &target,
+ KeyValueDB::Transaction t,
+ Header *old_header,
+ Header *new_header
+ );
+
+ void rename(
+ const Header header,
+ const coll_t &cid,
+ const ghobject_t &target,
+ KeyValueDB::Transaction t
+ );
+
+ /// Ensure that all previous operations are durable
+ int sync(const Header header, KeyValueDB::Transaction t);
+
+ static const string GLOBAL_STATE_KEY;
+ static const string PARENT_KEY;
+
+ static const string USER_PREFIX;
+ static const string INTERN_PREFIX;
+ static const string PARENT_PREFIX;
+ static const string COMPLETE_PREFIX;
+ static const string GHOBJECT_TO_SEQ_PREFIX;
+
+ static const string GHOBJECT_KEY_SEP_S;
+ static const char GHOBJECT_KEY_SEP_C;
+
+private:
+ /// Implicit lock on Header->seq
+
+ static string header_key(const coll_t &cid);
+ static string header_key(const coll_t &cid, const ghobject_t &oid);
+ static bool parse_header_key(const string &in, coll_t *c, ghobject_t *oid);
+
+ string seq_key(uint64_t seq) {
+ char buf[100];
+ snprintf(buf, sizeof(buf), "%.*" PRId64, (int)(2*sizeof(seq)), seq);
+ return string(buf);
+ }
+
+ string user_prefix(Header header, const string &prefix);
+ string complete_prefix(Header header);
+ string parent_seq_prefix(uint64_t seq);
+
+ class EmptyIteratorImpl : public ObjectMap::ObjectMapIteratorImpl {
+ public:
+ int seek_to_first() { return 0; }
+ int seek_to_last() { return 0; }
+ int upper_bound(const string &after) { return 0; }
+ int lower_bound(const string &to) { return 0; }
+ bool valid() { return false; }
+ int next() { assert(0); return 0; }
+ string key() { assert(0); return ""; }
+ bufferlist value() { assert(0); return bufferlist(); }
+ int status() { return 0; }
+ };
+
+
+ /// Iterator
+ class GenericObjectMapIteratorImpl : public ObjectMap::ObjectMapIteratorImpl {
+ public:
+ GenericObjectMap *map;
+
+ /// NOTE: implicit lock on header->seq AND for all ancestors
+ Header header;
+
+ /// parent_iter == NULL iff no parent
+ ceph::shared_ptr<GenericObjectMapIteratorImpl> parent_iter;
+ KeyValueDB::Iterator key_iter;
+ KeyValueDB::Iterator complete_iter;
+
+ /// cur_iter points to currently valid iterator
+ ceph::shared_ptr<ObjectMap::ObjectMapIteratorImpl> cur_iter;
+ int r;
+
+ /// init() called, key_iter, complete_iter, parent_iter filled in
+ bool ready;
+ /// past end
+ bool invalid;
+
+ string prefix;
+
+ GenericObjectMapIteratorImpl(GenericObjectMap *map, Header header,
+ const string &_prefix) : map(map), header(header), r(0), ready(false),
+ invalid(true), prefix(_prefix) { }
+ int seek_to_first();
+ int seek_to_last();
+ int upper_bound(const string &after);
+ int lower_bound(const string &to);
+ bool valid();
+ int next();
+ string key();
+ bufferlist value();
+ int status();
+
+ bool on_parent() {
+ return cur_iter == parent_iter;
+ }
+
+ /// skips to next valid parent entry
+ int next_parent();
+
+ /// Tests whether to_test is in complete region
+ int in_complete_region(const string &to_test, ///[in] key to test
+ string *begin, ///[out] beginning of region
+ string *end ///[out] end of region
+ ); ///< @returns true if to_test is in the complete region, else false
+
+ private:
+ int init();
+ bool valid_parent();
+ int adjust();
+ };
+
+ typedef ceph::shared_ptr<GenericObjectMapIteratorImpl> GenericObjectMapIterator;
+ GenericObjectMapIterator _get_iterator(Header header, string prefix) {
+ return GenericObjectMapIterator(new GenericObjectMapIteratorImpl(this, header, prefix));
+ }
+
+ /// Removes node corresponding to header
+ void clear_header(Header header, KeyValueDB::Transaction t);
+
+ /// Set node containing input to new contents
+ void set_parent_header(Header input, KeyValueDB::Transaction t);
+
+ /// Remove leaf node corresponding to oid in c
+ void remove_header(const coll_t &cid, const ghobject_t &oid, Header header,
+ KeyValueDB::Transaction t);
+
+ /**
+ * Generate new header for c oid with new seq number
+ *
+ * Has the side effect of syncronously saving the new GenericObjectMap state
+ */
+ Header _generate_new_header(const coll_t &cid, const ghobject_t &oid,
+ Header parent, KeyValueDB::Transaction t);
+ Header generate_new_header(const coll_t &cid, const ghobject_t &oid,
+ Header parent, KeyValueDB::Transaction t) {
+ Mutex::Locker l(header_lock);
+ return _generate_new_header(cid, oid, parent, t);
+ }
+
+ // Lookup leaf header for c oid
+ Header _lookup_header(const coll_t &cid, const ghobject_t &oid);
+
+ // Lookup header node for input
+ Header lookup_parent(Header input);
+
+ // Scan keys in header into out_keys and out_values (if nonnull)
+ int scan(Header header, const string &prefix, const set<string> &in_keys,
+ set<string> *out_keys, map<string, bufferlist> *out_values);
+
+ // Remove header and all related prefixes
+ int _clear(Header header, KeyValueDB::Transaction t);
+
+ // Adds to t operations necessary to add new_complete to the complete set
+ int merge_new_complete(Header header, const map<string, string> &new_complete,
+ GenericObjectMapIterator iter, KeyValueDB::Transaction t);
+
+ // Writes out State (mainly next_seq)
+ int write_state(KeyValueDB::Transaction _t);
+
+ // 0 if the complete set now contains all of key space, < 0 on error, 1 else
+ int need_parent(GenericObjectMapIterator iter);
+
+ // Copies header entry from parent @see rm_keys
+ int copy_up_header(Header header, KeyValueDB::Transaction t);
+
+ // Sets header @see set_header
+ void _set_header(Header header, const bufferlist &bl,
+ KeyValueDB::Transaction t);
+
+ /**
+ * Removes map header lock once Header is out of scope
+ * @see lookup_header
+ */
+ class RemoveMapHeaderOnDelete {
+ public:
+ GenericObjectMap *db;
+ coll_t cid;
+ ghobject_t oid;
+ RemoveMapHeaderOnDelete(GenericObjectMap *db, const coll_t &cid,
+ const ghobject_t &oid) :
+ db(db), cid(cid), oid(oid) {}
+ void operator() (_Header *header) {
+ Mutex::Locker l(db->header_lock);
+ db->map_header_in_use.erase(oid);
+ db->map_header_cond.Signal();
+ delete header;
+ }
+ };
+
+ /**
+ * Removes header seq lock once Header is out of scope
+ * @see lookup_parent
+ * @see generate_new_header
+ */
+ class RemoveOnDelete {
+ public:
+ GenericObjectMap *db;
+ RemoveOnDelete(GenericObjectMap *db) :
+ db(db) {}
+ void operator() (_Header *header) {
+ Mutex::Locker l(db->header_lock);
+ db->in_use.erase(header->seq);
+ db->header_cond.Signal();
+ delete header;
+ }
+ };
+ friend class RemoveOnDelete;
+};
+WRITE_CLASS_ENCODER(GenericObjectMap::_Header)
+WRITE_CLASS_ENCODER(GenericObjectMap::State)
+
+#endif