summaryrefslogtreecommitdiffstats
path: root/src/mds/SimpleLock.h
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 18:45:59 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 18:45:59 +0000
commit19fcec84d8d7d21e796c7624e521b60d28ee21ed (patch)
tree42d26aa27d1e3f7c0b8bd3fd14e7d7082f5008dc /src/mds/SimpleLock.h
parentInitial commit. (diff)
downloadceph-19fcec84d8d7d21e796c7624e521b60d28ee21ed.tar.xz
ceph-19fcec84d8d7d21e796c7624e521b60d28ee21ed.zip
Adding upstream version 16.2.11+ds.upstream/16.2.11+dsupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/mds/SimpleLock.h')
-rw-r--r--src/mds/SimpleLock.h667
1 files changed, 667 insertions, 0 deletions
diff --git a/src/mds/SimpleLock.h b/src/mds/SimpleLock.h
new file mode 100644
index 000000000..725c4488c
--- /dev/null
+++ b/src/mds/SimpleLock.h
@@ -0,0 +1,667 @@
+// -*- 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) 2004-2006 Sage Weil <sage@newdream.net>
+ *
+ * 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_SIMPLELOCK_H
+#define CEPH_SIMPLELOCK_H
+
+#include <boost/intrusive_ptr.hpp>
+
+#include "MDSCacheObject.h"
+#include "MDSContext.h"
+
+// -- lock types --
+// see CEPH_LOCK_*
+
+extern "C" {
+#include "locks.h"
+}
+
+#define CAP_ANY 0
+#define CAP_LONER 1
+#define CAP_XLOCKER 2
+
+struct MDLockCache;
+struct MDLockCacheItem;
+struct MutationImpl;
+typedef boost::intrusive_ptr<MutationImpl> MutationRef;
+
+struct LockType {
+ explicit LockType(int t) : type(t) {
+ switch (type) {
+ case CEPH_LOCK_DN:
+ case CEPH_LOCK_IAUTH:
+ case CEPH_LOCK_ILINK:
+ case CEPH_LOCK_IXATTR:
+ case CEPH_LOCK_ISNAP:
+ case CEPH_LOCK_IFLOCK:
+ case CEPH_LOCK_IPOLICY:
+ sm = &sm_simplelock;
+ break;
+ case CEPH_LOCK_IDFT:
+ case CEPH_LOCK_INEST:
+ sm = &sm_scatterlock;
+ break;
+ case CEPH_LOCK_IFILE:
+ sm = &sm_filelock;
+ break;
+ case CEPH_LOCK_DVERSION:
+ case CEPH_LOCK_IVERSION:
+ sm = &sm_locallock;
+ break;
+ default:
+ sm = 0;
+ }
+ }
+
+ int type;
+ const sm_t *sm;
+};
+
+
+class SimpleLock {
+public:
+ // waiting
+ static const uint64_t WAIT_RD = (1<<0); // to read
+ static const uint64_t WAIT_WR = (1<<1); // to write
+ static const uint64_t WAIT_XLOCK = (1<<2); // to xlock (** dup)
+ static const uint64_t WAIT_STABLE = (1<<2); // for a stable state
+ static const uint64_t WAIT_REMOTEXLOCK = (1<<3); // for a remote xlock
+ static const int WAIT_BITS = 4;
+ static const uint64_t WAIT_ALL = ((1<<WAIT_BITS)-1);
+
+ static std::string_view get_state_name(int n) {
+ switch (n) {
+ case LOCK_UNDEF: return "UNDEF";
+ case LOCK_SYNC: return "sync";
+ case LOCK_LOCK: return "lock";
+
+ case LOCK_PREXLOCK: return "prexlock";
+ case LOCK_XLOCK: return "xlock";
+ case LOCK_XLOCKDONE: return "xlockdone";
+ case LOCK_XLOCKSNAP: return "xlocksnap";
+ case LOCK_LOCK_XLOCK: return "lock->xlock";
+
+ case LOCK_SYNC_LOCK: return "sync->lock";
+ case LOCK_LOCK_SYNC: return "lock->sync";
+ case LOCK_REMOTEXLOCK: return "remote_xlock";
+ case LOCK_EXCL: return "excl";
+ case LOCK_EXCL_SYNC: return "excl->sync";
+ case LOCK_EXCL_LOCK: return "excl->lock";
+ case LOCK_SYNC_EXCL: return "sync->excl";
+ case LOCK_LOCK_EXCL: return "lock->excl";
+
+ case LOCK_XSYN: return "xsyn";
+ case LOCK_XSYN_EXCL: return "xsyn->excl";
+ case LOCK_EXCL_XSYN: return "excl->xsyn";
+ case LOCK_XSYN_SYNC: return "xsyn->sync";
+ case LOCK_XSYN_LOCK: return "xsyn->lock";
+ case LOCK_XSYN_MIX: return "xsyn->mix";
+
+ case LOCK_SYNC_MIX: return "sync->mix";
+ case LOCK_SYNC_MIX2: return "sync->mix(2)";
+ case LOCK_LOCK_TSYN: return "lock->tsyn";
+
+ case LOCK_MIX_LOCK: return "mix->lock";
+ case LOCK_MIX_LOCK2: return "mix->lock(2)";
+ case LOCK_MIX: return "mix";
+ case LOCK_MIX_TSYN: return "mix->tsyn";
+
+ case LOCK_TSYN_MIX: return "tsyn->mix";
+ case LOCK_TSYN_LOCK: return "tsyn->lock";
+ case LOCK_TSYN: return "tsyn";
+
+ case LOCK_MIX_SYNC: return "mix->sync";
+ case LOCK_MIX_SYNC2: return "mix->sync(2)";
+ case LOCK_EXCL_MIX: return "excl->mix";
+ case LOCK_MIX_EXCL: return "mix->excl";
+
+ case LOCK_PRE_SCAN: return "*->scan";
+ case LOCK_SCAN: return "scan";
+
+ case LOCK_SNAP_SYNC: return "snap->sync";
+
+ default: ceph_abort(); return std::string_view();
+ }
+ }
+
+ static std::string_view get_lock_type_name(int t) {
+ switch (t) {
+ case CEPH_LOCK_DN: return "dn";
+ case CEPH_LOCK_DVERSION: return "dversion";
+ case CEPH_LOCK_IVERSION: return "iversion";
+ case CEPH_LOCK_IFILE: return "ifile";
+ case CEPH_LOCK_IAUTH: return "iauth";
+ case CEPH_LOCK_ILINK: return "ilink";
+ case CEPH_LOCK_IDFT: return "idft";
+ case CEPH_LOCK_INEST: return "inest";
+ case CEPH_LOCK_IXATTR: return "ixattr";
+ case CEPH_LOCK_ISNAP: return "isnap";
+ case CEPH_LOCK_IFLOCK: return "iflock";
+ case CEPH_LOCK_IPOLICY: return "ipolicy";
+ default: return "unknown";
+ }
+ }
+
+ static std::string_view get_lock_action_name(int a) {
+ switch (a) {
+ case LOCK_AC_SYNC: return "sync";
+ case LOCK_AC_MIX: return "mix";
+ case LOCK_AC_LOCK: return "lock";
+ case LOCK_AC_LOCKFLUSHED: return "lockflushed";
+
+ case LOCK_AC_SYNCACK: return "syncack";
+ case LOCK_AC_MIXACK: return "mixack";
+ case LOCK_AC_LOCKACK: return "lockack";
+
+ case LOCK_AC_REQSCATTER: return "reqscatter";
+ case LOCK_AC_REQUNSCATTER: return "requnscatter";
+ case LOCK_AC_NUDGE: return "nudge";
+ case LOCK_AC_REQRDLOCK: return "reqrdlock";
+ default: return "???";
+ }
+ }
+
+ SimpleLock(MDSCacheObject *o, LockType *lt) :
+ type(lt),
+ parent(o)
+ {}
+ virtual ~SimpleLock() {}
+
+ client_t get_excl_client() const {
+ return have_more() ? more()->excl_client : -1;
+ }
+ void set_excl_client(client_t c) {
+ if (c < 0 && !have_more())
+ return; // default is -1
+ more()->excl_client = c;
+ }
+
+ virtual bool is_scatterlock() const {
+ return false;
+ }
+ virtual bool is_locallock() const {
+ return false;
+ }
+
+ // parent
+ MDSCacheObject *get_parent() { return parent; }
+ int get_type() const { return type->type; }
+ const sm_t* get_sm() const { return type->sm; }
+
+ int get_wait_shift() const;
+ int get_cap_shift() const;
+ int get_cap_mask() const;
+
+ void decode_locked_state(const ceph::buffer::list& bl) {
+ parent->decode_lock_state(type->type, bl);
+ }
+ void encode_locked_state(ceph::buffer::list& bl) {
+ parent->encode_lock_state(type->type, bl);
+ }
+ void finish_waiters(uint64_t mask, int r=0) {
+ parent->finish_waiting(mask << get_wait_shift(), r);
+ }
+ void take_waiting(uint64_t mask, MDSContext::vec& ls) {
+ parent->take_waiting(mask << get_wait_shift(), ls);
+ }
+ void add_waiter(uint64_t mask, MDSContext *c) {
+ parent->add_waiter((mask << get_wait_shift()) | MDSCacheObject::WAIT_ORDERED, c);
+ }
+ bool is_waiter_for(uint64_t mask) const {
+ return parent->is_waiter_for(mask << get_wait_shift());
+ }
+
+ bool is_cached() const {
+ return state_flags & CACHED;
+ }
+ void add_cache(MDLockCacheItem& item);
+ void remove_cache(MDLockCacheItem& item);
+ std::vector<MDLockCache*> get_active_caches();
+
+ // state
+ int get_state() const { return state; }
+ int set_state(int s) {
+ state = s;
+ //assert(!is_stable() || gather_set.size() == 0); // gather should be empty in stable states.
+ return s;
+ }
+ void set_state_rejoin(int s, MDSContext::vec& waiters, bool survivor) {
+ ceph_assert(!get_parent()->is_auth());
+
+ // If lock in the replica object was not in SYNC state when auth mds of the object failed.
+ // Auth mds of the object may take xlock on the lock and change the object when replaying
+ // unsafe requests.
+ if (!survivor || state != LOCK_SYNC)
+ mark_need_recover();
+
+ state = s;
+
+ if (is_stable())
+ take_waiting(SimpleLock::WAIT_ALL, waiters);
+ }
+
+ bool is_stable() const {
+ return get_sm()->states[state].next == 0;
+ }
+ bool is_unstable_and_locked() const {
+ return (!is_stable() && is_locked());
+ }
+ bool is_locked() const {
+ return is_rdlocked() || is_wrlocked() || is_xlocked();
+ }
+ int get_next_state() {
+ return get_sm()->states[state].next;
+ }
+
+ bool is_sync_and_unlocked() const {
+ return
+ get_state() == LOCK_SYNC &&
+ !is_rdlocked() &&
+ !is_leased() &&
+ !is_wrlocked() &&
+ !is_xlocked();
+ }
+
+ /*
+ bool fw_rdlock_to_auth() {
+ return get_sm()->states[state].can_rdlock == FW;
+ }
+ */
+ bool req_rdlock_from_auth() {
+ return get_sm()->states[state].can_rdlock == REQ;
+ }
+
+ // gather set
+ static std::set<int32_t> empty_gather_set;
+
+ // int32_t: <0 is client, >=0 is MDS rank
+ const std::set<int32_t>& get_gather_set() const {
+ return have_more() ? more()->gather_set : empty_gather_set;
+ }
+
+ void init_gather() {
+ for (const auto& p : parent->get_replicas()) {
+ more()->gather_set.insert(p.first);
+ }
+ }
+ bool is_gathering() const {
+ return have_more() && !more()->gather_set.empty();
+ }
+ bool is_gathering(int32_t i) const {
+ return have_more() && more()->gather_set.count(i);
+ }
+ void clear_gather() {
+ if (have_more())
+ more()->gather_set.clear();
+ }
+ void remove_gather(int32_t i) {
+ if (have_more())
+ more()->gather_set.erase(i);
+ }
+
+ virtual bool is_dirty() const { return false; }
+ virtual bool is_stale() const { return false; }
+ virtual bool is_flushing() const { return false; }
+ virtual bool is_flushed() const { return false; }
+ virtual void clear_flushed() { }
+
+ // can_*
+ bool can_lease(client_t client) const {
+ return get_sm()->states[state].can_lease == ANY ||
+ (get_sm()->states[state].can_lease == AUTH && parent->is_auth()) ||
+ (get_sm()->states[state].can_lease == XCL && client >= 0 && get_xlock_by_client() == client);
+ }
+ bool can_read(client_t client) const {
+ return get_sm()->states[state].can_read == ANY ||
+ (get_sm()->states[state].can_read == AUTH && parent->is_auth()) ||
+ (get_sm()->states[state].can_read == XCL && client >= 0 && get_xlock_by_client() == client);
+ }
+ bool can_read_projected(client_t client) const {
+ return get_sm()->states[state].can_read_projected == ANY ||
+ (get_sm()->states[state].can_read_projected == AUTH && parent->is_auth()) ||
+ (get_sm()->states[state].can_read_projected == XCL && client >= 0 && get_xlock_by_client() == client);
+ }
+ bool can_rdlock(client_t client) const {
+ return get_sm()->states[state].can_rdlock == ANY ||
+ (get_sm()->states[state].can_rdlock == AUTH && parent->is_auth()) ||
+ (get_sm()->states[state].can_rdlock == XCL && client >= 0 && get_xlock_by_client() == client);
+ }
+ bool can_wrlock(client_t client) const {
+ return get_sm()->states[state].can_wrlock == ANY ||
+ (get_sm()->states[state].can_wrlock == AUTH && parent->is_auth()) ||
+ (get_sm()->states[state].can_wrlock == XCL && client >= 0 && (get_xlock_by_client() == client ||
+ get_excl_client() == client));
+ }
+ bool can_force_wrlock(client_t client) const {
+ return get_sm()->states[state].can_force_wrlock == ANY ||
+ (get_sm()->states[state].can_force_wrlock == AUTH && parent->is_auth()) ||
+ (get_sm()->states[state].can_force_wrlock == XCL && client >= 0 && (get_xlock_by_client() == client ||
+ get_excl_client() == client));
+ }
+ bool can_xlock(client_t client) const {
+ return get_sm()->states[state].can_xlock == ANY ||
+ (get_sm()->states[state].can_xlock == AUTH && parent->is_auth()) ||
+ (get_sm()->states[state].can_xlock == XCL && client >= 0 && get_xlock_by_client() == client);
+ }
+
+ // rdlock
+ bool is_rdlocked() const { return num_rdlock > 0; }
+ int get_rdlock() {
+ if (!num_rdlock)
+ parent->get(MDSCacheObject::PIN_LOCK);
+ return ++num_rdlock;
+ }
+ int put_rdlock() {
+ ceph_assert(num_rdlock>0);
+ --num_rdlock;
+ if (num_rdlock == 0)
+ parent->put(MDSCacheObject::PIN_LOCK);
+ return num_rdlock;
+ }
+ int get_num_rdlocks() const {
+ return num_rdlock;
+ }
+
+ // wrlock
+ void get_wrlock(bool force=false) {
+ //assert(can_wrlock() || force);
+ if (more()->num_wrlock == 0)
+ parent->get(MDSCacheObject::PIN_LOCK);
+ ++more()->num_wrlock;
+ }
+ void put_wrlock() {
+ --more()->num_wrlock;
+ if (more()->num_wrlock == 0) {
+ parent->put(MDSCacheObject::PIN_LOCK);
+ try_clear_more();
+ }
+ }
+ bool is_wrlocked() const {
+ return have_more() && more()->num_wrlock > 0;
+ }
+ int get_num_wrlocks() const {
+ return have_more() ? more()->num_wrlock : 0;
+ }
+
+ // xlock
+ void get_xlock(MutationRef who, client_t client) {
+ ceph_assert(get_xlock_by() == MutationRef());
+ ceph_assert(state == LOCK_XLOCK || is_locallock() ||
+ state == LOCK_LOCK /* if we are a peer */);
+ parent->get(MDSCacheObject::PIN_LOCK);
+ more()->num_xlock++;
+ more()->xlock_by = who;
+ more()->xlock_by_client = client;
+ }
+ void set_xlock_done() {
+ ceph_assert(more()->xlock_by);
+ ceph_assert(state == LOCK_XLOCK || is_locallock() ||
+ state == LOCK_LOCK /* if we are a peer */);
+ if (!is_locallock())
+ state = LOCK_XLOCKDONE;
+ more()->xlock_by.reset();
+ }
+ void put_xlock() {
+ ceph_assert(state == LOCK_XLOCK || state == LOCK_XLOCKDONE ||
+ state == LOCK_XLOCKSNAP || state == LOCK_LOCK_XLOCK ||
+ state == LOCK_LOCK || /* if we are a leader of a peer */
+ is_locallock());
+ --more()->num_xlock;
+ parent->put(MDSCacheObject::PIN_LOCK);
+ if (more()->num_xlock == 0) {
+ more()->xlock_by.reset();
+ more()->xlock_by_client = -1;
+ try_clear_more();
+ }
+ }
+ bool is_xlocked() const {
+ return have_more() && more()->num_xlock > 0;
+ }
+ int get_num_xlocks() const {
+ return have_more() ? more()->num_xlock : 0;
+ }
+ client_t get_xlock_by_client() const {
+ return have_more() ? more()->xlock_by_client : -1;
+ }
+ bool is_xlocked_by_client(client_t c) const {
+ return have_more() ? more()->xlock_by_client == c : false;
+ }
+ MutationRef get_xlock_by() const {
+ return have_more() ? more()->xlock_by : MutationRef();
+ }
+
+ // lease
+ bool is_leased() const {
+ return state_flags & LEASED;
+ }
+ void get_client_lease() {
+ ceph_assert(!is_leased());
+ state_flags |= LEASED;
+ }
+ void put_client_lease() {
+ ceph_assert(is_leased());
+ state_flags &= ~LEASED;
+ }
+
+ bool needs_recover() const {
+ return state_flags & NEED_RECOVER;
+ }
+ void mark_need_recover() {
+ state_flags |= NEED_RECOVER;
+ }
+ void clear_need_recover() {
+ state_flags &= ~NEED_RECOVER;
+ }
+
+ // encode/decode
+ void encode(ceph::buffer::list& bl) const {
+ ENCODE_START(2, 2, bl);
+ encode(state, bl);
+ if (have_more())
+ encode(more()->gather_set, bl);
+ else
+ encode(empty_gather_set, bl);
+ ENCODE_FINISH(bl);
+ }
+ void decode(ceph::buffer::list::const_iterator& p) {
+ DECODE_START(2, p);
+ decode(state, p);
+ std::set<__s32> g;
+ decode(g, p);
+ if (!g.empty())
+ more()->gather_set.swap(g);
+ DECODE_FINISH(p);
+ }
+ void encode_state_for_replica(ceph::buffer::list& bl) const {
+ __s16 s = get_replica_state();
+ using ceph::encode;
+ encode(s, bl);
+ }
+ void decode_state(ceph::buffer::list::const_iterator& p, bool is_new=true) {
+ using ceph::decode;
+ __s16 s;
+ decode(s, p);
+ if (is_new)
+ state = s;
+ }
+ void decode_state_rejoin(ceph::buffer::list::const_iterator& p, MDSContext::vec& waiters, bool survivor) {
+ __s16 s;
+ using ceph::decode;
+ decode(s, p);
+ set_state_rejoin(s, waiters, survivor);
+ }
+
+ // caps
+ bool is_loner_mode() const {
+ return get_sm()->states[state].loner;
+ }
+ int gcaps_allowed_ever() const {
+ return parent->is_auth() ? get_sm()->allowed_ever_auth : get_sm()->allowed_ever_replica;
+ }
+ int gcaps_allowed(int who, int s=-1) const {
+ if (s < 0) s = state;
+ if (parent->is_auth()) {
+ if (get_xlock_by_client() >= 0 && who == CAP_XLOCKER)
+ return get_sm()->states[s].xlocker_caps | get_sm()->states[s].caps; // xlocker always gets more
+ else if (is_loner_mode() && who == CAP_ANY)
+ return get_sm()->states[s].caps;
+ else
+ return get_sm()->states[s].loner_caps | get_sm()->states[s].caps; // loner always gets more
+ } else
+ return get_sm()->states[s].replica_caps;
+ }
+ int gcaps_careful() const {
+ if (get_num_wrlocks())
+ return get_sm()->careful;
+ return 0;
+ }
+
+ int gcaps_xlocker_mask(client_t client) const {
+ if (client == get_xlock_by_client())
+ return type->type == CEPH_LOCK_IFILE ? 0xf : (CEPH_CAP_GSHARED|CEPH_CAP_GEXCL);
+ return 0;
+ }
+
+ // simplelock specifics
+ int get_replica_state() const {
+ return get_sm()->states[state].replica_state;
+ }
+ void export_twiddle() {
+ clear_gather();
+ state = get_replica_state();
+ }
+
+ bool remove_replica(int from) {
+ if (is_gathering(from)) {
+ remove_gather(from);
+ if (!is_gathering())
+ return true;
+ }
+ return false;
+ }
+ bool do_import(int from, int to) {
+ if (!is_stable()) {
+ remove_gather(from);
+ remove_gather(to);
+ if (!is_gathering())
+ return true;
+ }
+ if (!is_stable() && !is_gathering())
+ return true;
+ return false;
+ }
+
+ void _print(std::ostream& out) const {
+ out << get_lock_type_name(get_type()) << " ";
+ out << get_state_name(get_state());
+ if (!get_gather_set().empty())
+ out << " g=" << get_gather_set();
+ if (is_leased())
+ out << " l";
+ if (is_rdlocked())
+ out << " r=" << get_num_rdlocks();
+ if (is_wrlocked())
+ out << " w=" << get_num_wrlocks();
+ if (is_xlocked()) {
+ out << " x=" << get_num_xlocks();
+ if (get_xlock_by())
+ out << " by " << get_xlock_by();
+ }
+ /*if (is_stable())
+ out << " stable";
+ else
+ out << " unstable";
+ */
+ }
+
+ /**
+ * Write bare values (caller must be in an object section)
+ * to formatter, or nothing if is_sync_and_unlocked.
+ */
+ void dump(ceph::Formatter *f) const;
+
+ virtual void print(std::ostream& out) const {
+ out << "(";
+ _print(out);
+ out << ")";
+ }
+
+ LockType *type;
+
+protected:
+ // parent (what i lock)
+ MDSCacheObject *parent;
+
+ // lock state
+ __s16 state = LOCK_SYNC;
+ __s16 state_flags = 0;
+
+ enum {
+ LEASED = 1 << 0,
+ NEED_RECOVER = 1 << 1,
+ CACHED = 1 << 2,
+ };
+
+private:
+ // XXX not in mempool
+ struct unstable_bits_t {
+ unstable_bits_t();
+
+ bool empty() {
+ return
+ gather_set.empty() &&
+ num_wrlock == 0 &&
+ num_xlock == 0 &&
+ xlock_by.get() == NULL &&
+ xlock_by_client == -1 &&
+ excl_client == -1 &&
+ lock_caches.empty();
+ }
+
+ std::set<__s32> gather_set; // auth+rep. >= 0 is mds, < 0 is client
+
+ // local state
+ int num_wrlock = 0, num_xlock = 0;
+ MutationRef xlock_by;
+ client_t xlock_by_client = -1;
+ client_t excl_client = -1;
+
+ elist<MDLockCacheItem*> lock_caches;
+ };
+
+ bool have_more() const { return _unstable ? true : false; }
+ unstable_bits_t *more() const {
+ if (!_unstable)
+ _unstable.reset(new unstable_bits_t);
+ return _unstable.get();
+ }
+ void try_clear_more() {
+ if (_unstable && _unstable->empty()) {
+ _unstable.reset();
+ }
+ }
+
+ int num_rdlock = 0;
+
+ mutable std::unique_ptr<unstable_bits_t> _unstable;
+};
+WRITE_CLASS_ENCODER(SimpleLock)
+
+inline std::ostream& operator<<(std::ostream& out, const SimpleLock& l)
+{
+ l.print(out);
+ return out;
+}
+#endif