BitShares-Core  7.0.2
BitShares blockchain node software and command-line wallet software
undo_database.cpp
Go to the documentation of this file.
1 /*
2  * Copyright (c) 2015 Cryptonomex, Inc., and contributors.
3  *
4  * The MIT License
5  *
6  * Permission is hereby granted, free of charge, to any person obtaining a copy
7  * of this software and associated documentation files (the "Software"), to deal
8  * in the Software without restriction, including without limitation the rights
9  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10  * copies of the Software, and to permit persons to whom the Software is
11  * furnished to do so, subject to the following conditions:
12  *
13  * The above copyright notice and this permission notice shall be included in
14  * all copies or substantial portions of the Software.
15  *
16  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22  * THE SOFTWARE.
23  */
26 #include <fc/reflect/variant.hpp>
27 
28 namespace graphene { namespace db {
29 
30 void undo_database::enable() { _disabled = false; }
31 void undo_database::disable() { _disabled = true; }
32 
34 {
35  try {
36  if( _apply_undo ) _db.undo();
37  }
38  catch ( const fc::exception& e )
39  {
40  elog( "${e}", ("e",e.to_detail_string() ) );
41  std::terminate();
42  }
43  if( _disable_on_exit ) _db.disable();
44 }
45 
47 {
48  if( _disabled && !force_enable ) return session(*this);
49  bool disable_on_exit = _disabled && force_enable;
50  if( force_enable )
51  _disabled = false;
52 
53  while( size() > max_size() )
54  _stack.pop_front();
55 
56  _stack.emplace_back();
57  ++_active_sessions;
58  return session(*this, disable_on_exit );
59 }
60 void undo_database::on_create( const object& obj )
61 {
62  if( _disabled ) return;
63 
64  if( _stack.empty() )
65  _stack.emplace_back();
66  auto& state = _stack.back();
67  auto index_id = object_id_type( obj.id.space(), obj.id.type(), 0 );
68  auto itr = state.old_index_next_ids.find( index_id );
69  if( itr == state.old_index_next_ids.end() )
70  state.old_index_next_ids[index_id] = obj.id;
71  state.new_ids.insert(obj.id);
72 }
73 void undo_database::on_modify( const object& obj )
74 {
75  if( _disabled ) return;
76 
77  if( _stack.empty() )
78  _stack.emplace_back();
79  auto& state = _stack.back();
80  if( state.new_ids.find(obj.id) != state.new_ids.end() )
81  return;
82  auto itr = state.old_values.find(obj.id);
83  if( itr != state.old_values.end() ) return;
84  state.old_values[obj.id] = obj.clone();
85 }
86 void undo_database::on_remove( const object& obj )
87 {
88  if( _disabled ) return;
89 
90  if( _stack.empty() )
91  _stack.emplace_back();
92  undo_state& state = _stack.back();
93  if( state.new_ids.count(obj.id) > 0 )
94  {
95  state.new_ids.erase(obj.id);
96  return;
97  }
98  if( state.old_values.count(obj.id) > 0 )
99  {
100  state.removed[obj.id] = std::move(state.old_values[obj.id]);
101  state.old_values.erase(obj.id);
102  return;
103  }
104  if( state.removed.count(obj.id) > 0 ) return;
105  state.removed[obj.id] = obj.clone();
106 }
107 
108 void undo_database::undo()
109 { try {
110  FC_ASSERT( !_disabled );
111  FC_ASSERT( _active_sessions > 0 );
112  disable();
113 
114  auto& state = _stack.back();
115  for( auto& item : state.old_values )
116  {
117  _db.modify( _db.get_object( item.second->id ), [&]( object& obj ){ obj.move_from( *item.second ); } );
118  }
119 
120  for( auto ritr = state.new_ids.begin(); ritr != state.new_ids.end(); ++ritr )
121  {
122  _db.remove( _db.get_object(*ritr) );
123  }
124 
125  for( auto& item : state.old_index_next_ids )
126  {
127  _db.get_mutable_index( item.first.space(), item.first.type() ).set_next_id( item.second );
128  }
129 
130  for( auto& item : state.removed )
131  _db.insert( std::move(*item.second) );
132 
133  _stack.pop_back();
134  enable();
135  --_active_sessions;
137 
138 void undo_database::merge()
139 {
140  FC_ASSERT( _active_sessions > 0 );
141  if( _active_sessions == 1 && _stack.size() == 1 )
142  {
143  _stack.pop_back();
144  --_active_sessions;
145  return;
146  }
147  FC_ASSERT( _stack.size() >=2 );
148  auto& state = _stack.back();
149  auto& prev_state = _stack[_stack.size()-2];
150 
151  // An object's relationship to a state can be:
152  // in new_ids : new
153  // in old_values (was=X) : upd(was=X)
154  // in removed (was=X) : del(was=X)
155  // not in any of above : nop
156  //
157  // When merging A=prev_state and B=state we have a 4x4 matrix of all possibilities:
158  //
159  // |--------------------- B ----------------------|
160  //
161  // +------------+------------+------------+------------+
162  // | new | upd(was=Y) | del(was=Y) | nop |
163  // +------------+------------+------------+------------+------------+
164  // / | new | N/A | new A| nop C| new A|
165  // | +------------+------------+------------+------------+------------+
166  // | | upd(was=X) | N/A | upd(was=X)A| del(was=X)C| upd(was=X)A|
167  // A +------------+------------+------------+------------+------------+
168  // | | del(was=X) | N/A | N/A | N/A | del(was=X)A|
169  // | +------------+------------+------------+------------+------------+
170  // \ | nop | new B| upd(was=Y)B| del(was=Y)B| nop AB|
171  // +------------+------------+------------+------------+------------+
172  //
173  // Each entry was composed by labelling what should occur in the given case.
174  //
175  // Type A means the composition of states contains the same entry as the first of the two merged states for that object.
176  // Type B means the composition of states contains the same entry as the second of the two merged states for that object.
177  // Type C means the composition of states contains an entry different from either of the merged states for that object.
178  // Type N/A means the composition of states violates causal timing.
179  // Type AB means both type A and type B simultaneously.
180  //
181  // The merge() operation is defined as modifying prev_state in-place to be the state object which represents the composition of
182  // state A and B.
183  //
184  // Type A (and AB) can be implemented as a no-op; prev_state already contains the correct value for the merged state.
185  // Type B (and AB) can be implemented by copying from state to prev_state.
186  // Type C needs special case-by-case logic.
187  // Type N/A can be ignored or assert(false) as it can only occur if prev_state and state have illegal values
188  // (a serious logic error which should never happen).
189  //
190 
191  // We can only be outside type A/AB (the nop path) if B is not nop, so it suffices to iterate through B's three containers.
192 
193  // *+upd
194  for( auto& obj : state.old_values )
195  {
196  if( prev_state.new_ids.find(obj.second->id) != prev_state.new_ids.end() )
197  {
198  // new+upd -> new, type A
199  continue;
200  }
201  if( prev_state.old_values.find(obj.second->id) != prev_state.old_values.end() )
202  {
203  // upd(was=X) + upd(was=Y) -> upd(was=X), type A
204  continue;
205  }
206  // del+upd -> N/A
207  assert( prev_state.removed.find(obj.second->id) == prev_state.removed.end() );
208  // nop+upd(was=Y) -> upd(was=Y), type B
209  prev_state.old_values[obj.second->id] = std::move(obj.second);
210  }
211 
212  // *+new, but we assume the N/A cases don't happen, leaving type B nop+new -> new
213  for( auto id : state.new_ids )
214  prev_state.new_ids.insert(id);
215 
216  // old_index_next_ids can only be updated, iterate over *+upd cases
217  for( auto& item : state.old_index_next_ids )
218  {
219  if( prev_state.old_index_next_ids.find( item.first ) == prev_state.old_index_next_ids.end() )
220  {
221  // nop+upd(was=Y) -> upd(was=Y), type B
222  prev_state.old_index_next_ids[item.first] = item.second;
223  continue;
224  }
225  else
226  {
227  // upd(was=X)+upd(was=Y) -> upd(was=X), type A
228  // type A implementation is a no-op, as discussed above, so there is no code here
229  continue;
230  }
231  }
232 
233  // *+del
234  for( auto& obj : state.removed )
235  {
236  if( prev_state.new_ids.find(obj.second->id) != prev_state.new_ids.end() )
237  {
238  // new + del -> nop (type C)
239  prev_state.new_ids.erase(obj.second->id);
240  continue;
241  }
242  auto it = prev_state.old_values.find(obj.second->id);
243  if( it != prev_state.old_values.end() )
244  {
245  // upd(was=X) + del(was=Y) -> del(was=X)
246  prev_state.removed[obj.second->id] = std::move(it->second);
247  prev_state.old_values.erase(obj.second->id);
248  continue;
249  }
250  // del + del -> N/A
251  assert( prev_state.removed.find( obj.second->id ) == prev_state.removed.end() );
252  // nop + del(was=Y) -> del(was=Y)
253  prev_state.removed[obj.second->id] = std::move(obj.second);
254  }
255  _stack.pop_back();
256  --_active_sessions;
257 }
258 void undo_database::commit()
259 {
260  FC_ASSERT( _active_sessions > 0 );
261  --_active_sessions;
262 }
263 
265 {
266  FC_ASSERT( _active_sessions == 0 );
267  FC_ASSERT( !_stack.empty() );
268 
269  disable();
270  try {
271  auto& state = _stack.back();
272 
273  for( auto& item : state.old_values )
274  {
275  _db.modify( _db.get_object( item.second->id ), [&]( object& obj ){ obj.move_from( *item.second ); } );
276  }
277 
278  for( auto ritr = state.new_ids.begin(); ritr != state.new_ids.end(); ++ritr )
279  {
280  _db.remove( _db.get_object(*ritr) );
281  }
282 
283  for( auto& item : state.old_index_next_ids )
284  {
285  _db.get_mutable_index( item.first.space(), item.first.type() ).set_next_id( item.second );
286  }
287 
288  for( auto& item : state.removed )
289  _db.insert( std::move(*item.second) );
290 
291  _stack.pop_back();
292  }
293  catch ( const fc::exception& e )
294  {
295  elog( "error popping commit ${e}", ("e", e.to_detail_string() ) );
296  enable();
297  throw;
298  }
299  enable();
300 }
302 {
303  FC_ASSERT( !_stack.empty() );
304  return _stack.back();
305 }
306 
307 } } // graphene::db
FC_CAPTURE_AND_RETHROW
#define FC_CAPTURE_AND_RETHROW(...)
Definition: exception.hpp:479
graphene::db::undo_database::size
std::size_t size() const
Definition: undo_database.hpp:120
graphene::db::object::id
object_id_type id
Definition: object.hpp:69
fc::exception
Used to generate a useful error report when an exception is thrown.
Definition: exception.hpp:56
variant.hpp
graphene::db::object_database::insert
const object & insert(object &&obj)
Definition: object_database.hpp:96
graphene::db::undo_database::enable
void enable()
Definition: undo_database.cpp:30
graphene::db::undo_database::head
const undo_state & head() const
Definition: undo_database.cpp:301
graphene::db::undo_database::start_undo_session
session start_undo_session(bool force_enable=false)
Definition: undo_database.cpp:46
graphene::db::undo_state::old_values
std::unordered_map< object_id_type, std::unique_ptr< object > > old_values
Definition: undo_database.hpp:35
graphene::db::object_database::get_mutable_index
index & get_mutable_index()
Definition: object_database.hpp:179
graphene::db::undo_database::session::~session
~session()
Definition: undo_database.cpp:33
graphene::db::object::clone
virtual std::unique_ptr< object > clone() const =0
graphene::db::undo_database::max_size
size_t max_size() const
Definition: undo_database.hpp:122
graphene::db::object_database::get_object
const object & get_object(const object_id_type &id) const
Definition: object_database.cpp:47
graphene::db::undo_state::new_ids
std::unordered_set< object_id_type > new_ids
Definition: undo_database.hpp:37
graphene::db::undo_database::on_create
void on_create(const object &obj)
Definition: undo_database.cpp:60
graphene::db::object_id_type::space
uint8_t space() const
Definition: object_id.hpp:46
fc::exception::to_detail_string
std::string to_detail_string(log_level ll=log_level::all) const
Definition: exception.cpp:183
graphene::db::undo_database::pop_commit
void pop_commit()
Definition: undo_database.cpp:264
graphene::db::undo_database::on_remove
void on_remove(const object &obj)
Definition: undo_database.cpp:86
FC_ASSERT
#define FC_ASSERT(TEST,...)
Checks a condition and throws an assert_exception if the test is FALSE.
Definition: exception.hpp:345
graphene::db::object_id_type
Definition: object_id.hpp:30
graphene::db::undo_database::disable
void disable()
Definition: undo_database.cpp:31
graphene::db::undo_state::removed
std::unordered_map< object_id_type, std::unique_ptr< object > > removed
Definition: undo_database.hpp:38
graphene::db::index::set_next_id
virtual void set_next_id(object_id_type id)=0
graphene::db::object_database::remove
void remove(const object &obj)
Definition: object_database.hpp:97
graphene::db::object_id_type::type
uint8_t type() const
Definition: object_id.hpp:47
graphene::db::undo_state
Definition: undo_database.hpp:33
undo_database.hpp
graphene::db::undo_database::session
Definition: undo_database.hpp:52
object_database.hpp
graphene::db::undo_database::on_modify
void on_modify(const object &obj)
Definition: undo_database.cpp:73
graphene
Definition: api.cpp:48
graphene::db::object_database::modify
void modify(const T &obj, const Lambda &m)
Definition: object_database.hpp:99
elog
#define elog(FORMAT,...)
Definition: logger.hpp:129