blob: 4bbf76139311704f09d6ff582453f13ff8b5752b [file] [log] [blame]
.. Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
.. include:: ../../common.defs
.. highlight:: cpp
.. default-domain:: cpp
.. |AcidPtr| replace:: :class:`AcidPtr`
.. |AcidCommitPtr| replace:: :class:`AcidCommitPtr`
.. _ACIDPTR:
AcidPtr & AcidCommitPtr
***********************
Synopsis
++++++++
.. code-block:: cpp
#include "tscore/AcidPtr.h"
|AcidPtr| provides atomic access to a std::shared_ptr.
|AcidCommitPtr| provides exclusive write access to data.
Description
+++++++++++
Provides transparent interface for "copy on write" and "commit when done" in a familiar unique_ptr style.
Named after the desirable properties of a database, ACID_ acronym:
* Atomic - reads and writes avoid skew, by using mutex locks.
* Consistent - data can only be changed by a commit, and only one commit can exist at a time per data.
* Isolated - commits of a single point of data are not concurrent. But commits of separate data can be concurrent.
* Durable - shared_ptr is used to keep older versions of data in memory while references exist.
.. _ACID: https://en.wikipedia.org/wiki/ACID_(computer_science)
.. uml:
class AcidPtr<T> {
-std::shared_ptr<T> data
----
+AcidPtr()
+AcidPtr(T*)
+std::shared_ptr<const T> getPtr()
+void commit(T*)
+AcidCommmitPtr<T> startCommit()
----
~_finishCommit(T*)
}
class AcidCommitPtr<T> {
-AcidPtr& data_ptr
-std::unique_lock commit_lock
----
-AcidCommmitPtr() = delete
+AcidCommmitPtr(AcidPtr&)
+~AcidCommmitPtr()
+void abort()
}
class std::unique_ptr {
}
AcidCommitPtr--|>std::unique_ptr
Performance
-----------
Note this code is currently implemented with mutex locks, it is expected to be fast due to the very brief duration of each lock. It would be plausible to change the implementation to use atomics or spin locks.
|AcidCommitPtr|
* On construction, duplicate values from a shared_ptr to a unique_ptr. (aka copy read to write memory)
* On destruction, move value ptr to shared_ptr. (aka move write ptr to read ptr)
The |AcidCommitPtr| executes this transparent to the writer code. It copies the data on construction, and finalizes on destruction. A MemLock is used to allow exclusive read and write access, however the access is made to as fast as possible.
Use Cases
---------
Implemented for use |ACIDPTR| interface in :class:`Extendible`. But could be used elsewhere without modification.
.. uml::
:align: center
title Read while Write
actor ExistingReaders
actor NewReaders
actor Writer
storage AcidPtr
card "x=foo" as Data
card "x=bar" as DataPrime
ExistingReaders --> Data : read only
AcidPtr -> Data : shared_ptr
NewReaders --> AcidPtr : read only
Writer --> DataPrime : read/write
Data .> DataPrime : copy
When the writer is done, :func:`~AcidCommitPtr::~AcidCommitPtr()` is called and its |AcidPtr| is updated to point at the written copy, so that future read requests will use it. Existing reader will continue to use the old data.
.. uml::
:align: center
title Write Finalize
actor ExistingReaders
actor NewReaders
storage AcidPtr
card "x=foo" as Data
card "x=bar" as DataPrime
ExistingReaders --> Data : read only
AcidPtr -> DataPrime : shared_ptr
NewReaders --> AcidPtr : read only
.. uml::
:align: center
title AcidPtr Reader/Reader Contention
box "MemLock"
participant "Access" as AccessMutex
end box
participant AcidPtr
actor Reader_A #green
actor Reader_B #red
Reader_A -[#green]> AcidPtr: getPtr()
AcidPtr <[#green]- AccessMutex: lock
activate AccessMutex #green
Reader_B -> AcidPtr: getPtr()
AcidPtr -[#green]> Reader_A: copy const shared_ptr
activate Reader_A
note left
Contention limited to
duration of shared_ptr copy.
(internally defined)
end note
AcidPtr -[#green]> AccessMutex: unlock
deactivate AccessMutex
AcidPtr <- AccessMutex: lock
activate AccessMutex #red
AcidPtr -> Reader_B: copy const shared_ptr
activate Reader_B
AcidPtr -> AccessMutex: unlock
deactivate AccessMutex
.. uml::
:align: center
Title AcidPtr Writer/Reader Contention
box "MemLock"
participant "Access" as AccessMutex
participant "Write" as WriteMutex
end box
participant AcidPtr
actor Writer #red
actor Reader #green
Writer -> AcidPtr: startCommit()
AcidPtr <- WriteMutex: lock
activate WriteMutex #red
AcidPtr -> Writer: copy to AcidCommitPtr
activate Writer
hnote over Writer #pink
update AcidCommitPtr
end hnote
Reader -[#green]> AcidPtr: getPtr()
AcidPtr <[#green]- AccessMutex: lock
activate AccessMutex #green
Writer -> AcidPtr: ~AcidCommitPtr()
deactivate Writer
AcidPtr -[#green]> Reader: copy const shared_ptr
activate Reader
note left
Contention limited to duration
of shared_ptr copy/reset.
(internally defined)
end note
AcidPtr -[#green]> AccessMutex: unlock
deactivate AccessMutex
AcidPtr <- AccessMutex: lock
activate AccessMutex #red
hnote over Reader #lightgreen
use shared copy
end hnote
AcidPtr -> AcidPtr: reset shared_ptr
AcidPtr -> AccessMutex: unlock
deactivate AccessMutex
AcidPtr -> WriteMutex: unlock
deactivate WriteMutex
.. uml::
:align: center
Title AcidPtr Writer/Writer Contention
box "MemLock"
participant "Access" as AccessMutex
participant "Write" as WriteMutex
end box
participant AcidPtr
actor Writer_A #red
actor Writer_B #blue
Writer_A -> AcidPtr: startCommit
AcidPtr <- WriteMutex: lock
activate WriteMutex #red
AcidPtr -> Writer_A: copy to AcidCommitPtr
activate Writer_A
Writer_B -[#blue]> AcidPtr: startCommit
hnote over Writer_A #pink
update AcidCommitPtr
end hnote
note over Writer_A
Contention for duration
of AcidCommitPtr scope.
(externally defined)
end note
Writer_A -> AcidPtr: ~AcidCommitPtr()
deactivate Writer_A
AcidPtr <- AccessMutex: lock
activate AccessMutex #red
AcidPtr -> AcidPtr: reset shared_ptr
AcidPtr -> AccessMutex: unlock
deactivate AccessMutex
AcidPtr -> WriteMutex: unlock
deactivate WriteMutex
AcidPtr <[#blue]- WriteMutex: lock
activate WriteMutex #blue
AcidPtr -[#blue]> Writer_B: copy to AcidCommitPtr
activate Writer_B
hnote over Writer_B #lightblue
update AcidCommitPtr
end hnote
Writer_B -[#blue]> AcidPtr: ~AcidCommitPtr()
deactivate Writer_B
deactivate AccessMutex
AcidPtr <[#blue]- AccessMutex: lock
activate AccessMutex #blue
AcidPtr -[#blue]> AcidPtr: reset shared_ptr
AcidPtr -[#blue]> AccessMutex: unlock
deactivate AccessMutex
AcidPtr -[#blue]> WriteMutex: unlock
deactivate WriteMutex
Reference
+++++++++
.. class:: template<typename T> AcidPtr
.. function:: template<typename T> const std::shared_ptr<const T> getPtr() const
.. class:: template<typename T> AcidCommitPtr
.. function:: template<> ~AcidCommitPtr()
.. function:: template<T> AcidCommitPtr<T> startCommit()
.. function:: template<> void abort()