File: transfer.cc

package info (click to toggle)
tkrzw 1.0.32-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 4,820 kB
  • sloc: cpp: 64,785; ansic: 2,079; makefile: 1,125
file content (150 lines) | stat: -rw-r--r-- 6,062 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
/*************************************************************************************************
 * Example of money transfer using lambda processors
 *
 * Copyright 2020 Google LLC
 * Licensed 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
 *     https://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 "tkrzw_dbm_hash.h"
#include "tkrzw_str_util.h"

using namespace tkrzw;

Status Transfer(DBM* dbm, std::string_view src_key, std::string_view dest_key,
                int64_t amount) {
  Status op_status(Status::SUCCESS);

  // Callback to check the destination account.
  auto check_dest =
      [&](std::string_view key, std::string_view value) -> std::string_view {
        if (value.data() == DBM::RecordProcessor::NOOP.data()) {
          op_status.Set(Status::NOT_FOUND_ERROR, "no such destination account");
        }
        return DBM::RecordProcessor::NOOP;
      };

  // Callback to operate the source account.
  std::string new_src_value;
  auto proc_src =
      [&](std::string_view key, std::string_view value) -> std::string_view {
        if (!op_status.IsOK()) {
          return DBM::RecordProcessor::NOOP;
        }
        if (value.data() == DBM::RecordProcessor::NOOP.data()) {
          op_status.Set(Status::NOT_FOUND_ERROR, "no such source account");
          return DBM::RecordProcessor::NOOP;
        }
        const int64_t current_balance = StrToInt(value);
        if (current_balance < amount) {
          op_status.Set(Status::INFEASIBLE_ERROR, "insufficient balance");
          return DBM::RecordProcessor::NOOP;
        }
        new_src_value = ToString(current_balance - amount);
        return new_src_value;
      };

  // Callback to operate the destination account.
  std::string new_dest_value;
  auto proc_dest =
      [&](std::string_view key, std::string_view value) -> std::string_view {
        if (!op_status.IsOK()) {
          return DBM::RecordProcessor::NOOP;
        }
        if (value.data() == DBM::RecordProcessor::NOOP.data()) {
          op_status.Set(Status::APPLICATION_ERROR, "inconsistent transaction");
          return DBM::RecordProcessor::NOOP;
        }
        const int64_t current_balance = StrToInt(value);
        new_dest_value = ToString(current_balance + amount);
        return new_dest_value;
      };

  // Invokes the three callbacks in sequence atomically.
  const Status status = dbm->ProcessMulti({
      {dest_key, check_dest},
      {src_key, proc_src},
      {dest_key, proc_dest}}, true);
  if (!status.IsOK()) {
    return status;
  }
  return op_status;
}

Status TransferByCompareExchange(
    DBM* dbm, std::string_view src_key, std::string_view dest_key, int64_t amount) {
  // Repeats the transaction until success or failure is determined.
  constexpr int32_t max_tries = 100;
  for (int32_t num_tries = 0; num_tries < max_tries; num_tries++) {

    // Gets the balance of the source account.
    std::string src_value;
    Status status = dbm->Get(src_key, &src_value);
    if (!status.IsOK()) {
      return status;
    }
    const int64_t src_balance = StrToInt(src_value);
    if (src_balance < amount) {
      return Status(Status::INFEASIBLE_ERROR, "insufficient balance");
    }

    // Gets the balance of the destination account.
    std::string dest_value;
    status = dbm->Get(dest_key, &dest_value);
    if (!status.IsOK()) {
      return status;
    }
    const int64_t dest_balance = StrToInt(dest_value);

    // Generate new values for the new balances.
    const std::string src_new_value = ToString(src_balance - amount);
    const std::string dest_new_value = ToString(dest_balance + amount);
    
    // Finish the transaction atomically if the balances are not modified.
    const std::vector<std::pair<std::string_view, std::string_view>> expected =
        {{src_key, src_value}, {dest_key, dest_value}};
    const std::vector<std::pair<std::string_view, std::string_view>> desired =
        {{src_key, src_new_value}, {dest_key, dest_new_value}};
    status = dbm->CompareExchangeMulti(expected, desired);
    if (status.IsOK()) {
      return Status(Status::SUCCESS);
    }
    if (status != Status::INFEASIBLE_ERROR) {
      return status;
    }
  }

  return Status(Status::INFEASIBLE_ERROR, "too busy");
}

int main(int argc, char** argv) {
  HashDBM dbm;
  dbm.Open("casket.tkh", true, File::OPEN_TRUNCATE).OrDie();
  dbm.Set("A", "10000", File::OPEN_TRUNCATE).OrDie();
  dbm.Set("B", "5000", File::OPEN_TRUNCATE).OrDie();
  std::cout << "A > B by 1000 : " << Transfer(&dbm, "A", "B", 1000) << std::endl;
  std::cout << "A > B by 10000 : " << Transfer(&dbm, "A", "B", 10000) << std::endl;
  std::cout << "B > C by 1000 : " << Transfer(&dbm, "A", "C", 1000) << std::endl;
  std::cout << "B > A by 4000 : " << Transfer(&dbm, "B", "A", 4000) << std::endl;
  std::cout << "A:" << dbm.GetSimple("A", "*") << std::endl;
  std::cout << "B:" << dbm.GetSimple("B", "*") << std::endl;
  std::cout << "A > B by 10000 : "
            << TransferByCompareExchange(&dbm, "A", "B", 10000) << std::endl;
  std::cout << "A > B by 5000 : "
            << TransferByCompareExchange(&dbm, "A", "B", 5000) << std::endl;
  std::cout << "A > C by 1000 : "
            << TransferByCompareExchange(&dbm, "A", "C", 1000) << std::endl;
  std::cout << "B > A by 2000 : "
            << TransferByCompareExchange(&dbm, "B", "A", 2000) << std::endl;
  std::cout << "A:" << dbm.GetSimple("A", "*") << std::endl;
  std::cout << "B:" << dbm.GetSimple("B", "*") << std::endl;
  dbm.Close().OrDie();
  return 0;
}

// END OF FILE