RTBKit  0.9
Open-source framework to create real-time ad bidding systems.
core/banker/account.h
00001 /* account.h                                                       -*- C++ -*-
00002    Jeremy Barnes, 16 November 2012
00003    Copyright (c) 2012 Datacratic Inc.  All rights reserved.
00004 
00005 */
00006 
00007 #pragma once
00008 
00009 #include <string>
00010 #include <vector>
00011 #include <unordered_map>
00012 #include <memory>
00013 #include <unordered_set>
00014 #include "rtbkit/common/currency.h"
00015 #include "rtbkit/common/account_key.h"
00016 #include "soa/types/date.h"
00017 #include "jml/utils/string_functions.h"
00018 #include <mutex>
00019 #include <thread>
00020 #include "jml/arch/spinlock.h"
00021 
00022 namespace Datacratic {
00023     struct EventRecorder;
00024 }
00025 
00026 namespace RTBKIT {
00027 using namespace Datacratic;
00028 
00029 struct Account;
00030 
00031 std::ostream & operator << (std::ostream & stream, const Account & account);
00032 
00033 struct ShadowAccount;
00034 
00035 std::ostream &
00036 operator << (std::ostream & stream, const ShadowAccount & account);
00037 
00038 
00039 /*****************************************************************************/
00040 /* ACCOUNT TYPE                                                              */
00041 /*****************************************************************************/
00042 
00043 enum AccountType {
00044     AT_NONE,
00045     AT_BUDGET,      
00046     AT_SPEND        
00047 };
00048 
00049 inline AccountType restDecode(const std::string & param, AccountType *)
00050 {
00051     if (param == "none")
00052         return AT_NONE;
00053     else if (param == "budget")
00054         return AT_BUDGET;
00055     else if (param == "spend")
00056         return AT_SPEND;
00057     else throw ML::Exception("unknown account type " + param);
00058 }
00059 
00060 extern AccountType AccountTypeFromString(const std::string & param);
00061 extern const std::string AccountTypeToString(enum AccountType type);
00062 
00063 /*****************************************************************************/
00064 /* ACCOUNT                                                                   */
00065 /*****************************************************************************/
00066 
00069 struct Account {
00070     Account()
00071         : type(AT_NONE)
00072     {
00073     }
00074 
00075     AccountType type;
00076 
00077     //mutable Date lastAccess;
00078     //Date lastModification;
00079 
00080     // On the Credit side...
00081     CurrencyPool budgetIncreases;    
00082     CurrencyPool budgetDecreases;    
00083     CurrencyPool recycledIn;         
00084     CurrencyPool allocatedIn;
00085     CurrencyPool commitmentsRetired; 
00086     CurrencyPool adjustmentsIn;      
00087 
00088     // On the Debit side...
00089     CurrencyPool recycledOut;
00090     CurrencyPool allocatedOut;
00091     CurrencyPool commitmentsMade;
00092     CurrencyPool adjustmentsOut;  
00093     CurrencyPool spent;           
00094 
00095     CurrencyPool balance;       
00096 
00097     // Extra information tracked, but not used in any calculations
00098     LineItems lineItems;            
00099     LineItems adjustmentLineItems;  
00100 
00101     // Invariant: sum(Credit Side) = sum(Debit Side)
00102 
00103 public:
00104     bool isSameOrPastVersion(const Account & otherAccount) const
00105     {
00106         /* All the amounts in the storage accounts must have a counterpart in
00107          * the banker accounts and their value must be inferior or equal to
00108          * the corresponding amounts in the banker. */
00109         return (budgetIncreases.isSameOrPastVersion(otherAccount.budgetIncreases)
00110                 && budgetDecreases.isSameOrPastVersion(otherAccount.budgetDecreases)
00111                 && recycledIn.isSameOrPastVersion(otherAccount.recycledIn)
00112                 && allocatedIn.isSameOrPastVersion(otherAccount.allocatedIn)
00113                 && commitmentsRetired.isSameOrPastVersion(otherAccount.commitmentsRetired)
00114                 && adjustmentsIn.isSameOrPastVersion(otherAccount.adjustmentsIn)
00115 
00116                 && recycledOut.isSameOrPastVersion(otherAccount.recycledOut)
00117                 && allocatedOut.isSameOrPastVersion(otherAccount.allocatedOut)
00118                 && commitmentsMade.isSameOrPastVersion(otherAccount.commitmentsMade)
00119                 && adjustmentsOut.isSameOrPastVersion(otherAccount.adjustmentsOut)
00120                 && spent.isSameOrPastVersion(otherAccount.spent));
00121     }
00122 
00123     Json::Value toJson() const
00124     {
00125         // checkInvariants();
00126 
00127         Json::Value result(Json::objectValue);
00128         result["md"]["objectType"] = "Account";
00129         result["md"]["version"] = 1;
00130         result["type"] = AccountTypeToString(type);
00131         result["budgetIncreases"] = budgetIncreases.toJson();
00132         result["budgetDecreases"] = budgetDecreases.toJson();
00133         result["spent"] = spent.toJson();
00134         result["recycledIn"] = recycledIn.toJson();
00135         result["recycledOut"] = recycledOut.toJson();
00136         result["allocatedIn"] = allocatedIn.toJson();
00137         result["allocatedOut"] = allocatedOut.toJson();
00138         result["commitmentsMade"] = commitmentsMade.toJson();
00139         result["commitmentsRetired"] = commitmentsRetired.toJson();
00140         result["adjustmentsIn"] = adjustmentsIn.toJson();
00141         result["adjustmentsOut"] = adjustmentsOut.toJson();
00142         result["lineItems"] = lineItems.toJson();
00143         result["adjustmentLineItems"] = adjustmentLineItems.toJson();
00144 
00145         return result;
00146     }
00147 
00148     static const Account fromJson(const Json::Value & json)
00149     {
00150         Account result;
00151         ExcAssertEqual(json["md"]["objectType"].asString(), "Account");
00152         ExcAssertEqual(json["md"]["version"].asInt(), 1);
00153 
00154         result.type = AccountTypeFromString(json["type"].asString());
00155         if (json.isMember("budget")) {
00156             result.budgetIncreases = CurrencyPool::fromJson(json["budget"]);
00157             result.budgetDecreases = CurrencyPool::fromJson(json["adjustmentsOut"]);
00158             result.adjustmentsOut = CurrencyPool();
00159         }
00160         else {
00161             result.budgetIncreases = CurrencyPool::fromJson(json["budgetIncreases"]);
00162             result.budgetDecreases = CurrencyPool::fromJson(json["budgetDecreases"]);
00163             result.adjustmentsOut = CurrencyPool::fromJson(json["adjustmentsOut"]);
00164         }
00165         result.spent = CurrencyPool::fromJson(json["spent"]);
00166         result.recycledIn = CurrencyPool::fromJson(json["recycledIn"]);
00167         result.recycledOut = CurrencyPool::fromJson(json["recycledOut"]);
00168         result.allocatedIn = CurrencyPool::fromJson(json["allocatedIn"]);
00169         result.allocatedOut = CurrencyPool::fromJson(json["allocatedOut"]);
00170         result.commitmentsMade = CurrencyPool::fromJson(json["commitmentsMade"]);
00171         result.commitmentsRetired = CurrencyPool::fromJson(json["commitmentsRetired"]);
00172         result.adjustmentsIn = CurrencyPool::fromJson(json["adjustmentsIn"]);
00173         result.lineItems = LineItems::fromJson(json["lineItems"]);
00174         result.adjustmentLineItems = LineItems::fromJson(json["adjustmentLineItems"]);
00175         result.balance = ((result.budgetIncreases
00176                              + result.recycledIn
00177                              + result.commitmentsRetired
00178                              + result.adjustmentsIn
00179                              + result.allocatedIn)
00180                             - (result.budgetDecreases
00181                                + result.recycledOut
00182                                + result.commitmentsMade
00183                                + result.spent
00184                                + result.adjustmentsOut
00185                                + result.balance
00186                                + result.allocatedOut));
00187 
00188         result.checkInvariants();
00189 
00190         return result;
00191     }
00192 
00193     /*************************************************************************/
00194     /* DERIVED QUANTITIES                                                    */
00195     /*************************************************************************/
00196 
00199     CurrencyPool getRecycledAvail() const
00200     {
00201         return (recycledIn - recycledOut).nonNegative();
00202     }
00203 
00204     CurrencyPool getBudget() const;
00205 
00209     CurrencyPool getNetBudget() const;
00210 
00211     /*************************************************************************/
00212     /* INVARIANTS                                                            */
00213     /*************************************************************************/
00214     
00215     void checkInvariants(const char * whereFrom = "") const
00216     {
00217         try {
00218 
00219             // Everything but balance must be positive
00220             ExcAssert(budgetIncreases.isNonNegative());
00221             ExcAssert(budgetDecreases.isNonNegative());
00222             ExcAssert(recycledIn.isNonNegative());
00223             ExcAssert(recycledOut.isNonNegative());
00224             ExcAssert(commitmentsRetired.isNonNegative());
00225             ExcAssert(recycledOut.isNonNegative());
00226             ExcAssert(commitmentsMade.isNonNegative());
00227             ExcAssert(spent.isNonNegative());
00228             ExcAssert(adjustmentsIn.isNonNegative());
00229             ExcAssert(adjustmentsOut.isNonNegative());
00230             ExcAssert(allocatedIn.isNonNegative());
00231             ExcAssert(allocatedOut.isNonNegative());
00232 
00233             // Credit and debit sides must balance out
00234             CurrencyPool credit = (budgetIncreases + recycledIn + commitmentsRetired
00235                                    + adjustmentsIn + allocatedIn);
00236             CurrencyPool debit = (budgetDecreases + recycledOut + commitmentsMade + spent
00237                                   + adjustmentsOut + balance
00238                                   + allocatedOut);
00239             ExcAssertEqual(credit, debit);
00240         } catch (...) {
00241             using namespace std;
00242             cerr << "error on account " << *this << " checking invariants at "
00243                  << whereFrom << endl;
00244             throw;
00245         }
00246         //...
00247     }
00248 
00249     /*************************************************************************/
00250     /* TRANSFER OPERATIONS (ONE SIDED)                                       */
00251     /*************************************************************************/
00252 
00253     /* These operations need to be paired with a corresponding operation on
00254        the other side.
00255     */
00256 
00260     CurrencyPool recuperate()
00261     {
00262         auto result = balance;
00263         recycledOut += balance;
00264         balance.clear();
00265 
00266         checkInvariants();
00267 
00268         return result;
00269     }
00270     
00274     void recycle(const CurrencyPool & recuperated)
00275     {
00276         ExcAssert(recuperated.isNonNegative());
00277 
00278         recycledIn += recuperated;
00279         balance += recuperated;
00280 
00281         checkInvariants();
00282     }
00283 
00287     void setBudget(const CurrencyPool & newBudget);
00288 
00293     CurrencyPool setBalance(Account & parentAccount,
00294                             const CurrencyPool & newBalance)
00295     {
00296         checkInvariants("entry to setBalance");
00297 
00298         auto before = *this;
00299         auto parentBefore = parentAccount;
00300 
00301         CurrencyPool requiredTotal = newBalance - balance;
00302 
00303         // Some amount needs to be transferred in, and some amount out
00304         CurrencyPool requiredIn = requiredTotal.nonNegative();
00305         CurrencyPool requiredOut = requiredIn - requiredTotal;
00306 
00307         ExcAssert(requiredIn.isNonNegative());
00308         ExcAssert(requiredOut.isNonNegative());
00309 
00310         CurrencyPool toTransfer = parentAccount.balance.limit(requiredIn);
00311 
00312         using namespace std;
00313 
00314         bool debug = false;
00315 
00316         // First take it from the recycled...
00317         CurrencyPool parentRecycledAvail
00318             = parentAccount.getRecycledAvail();
00319         CurrencyPool fromRecycled = parentRecycledAvail.limit(toTransfer);
00320         CurrencyPool toRecycled = requiredOut;
00321 
00322         if (debug) {
00323             cerr << "newBalance = " << newBalance << endl;
00324             cerr << "balance = " << balance << endl;
00325             cerr << "requiredTotal = " << requiredTotal << endl;
00326             cerr << "requiredIn = " << requiredIn << endl;
00327             cerr << "requiredOut = " << requiredOut << endl;
00328             cerr << "toTransfer = " << toTransfer << endl;
00329             cerr << "parentRecycledAvail = " << parentRecycledAvail << endl;
00330             cerr << "fromRecycled = " << fromRecycled << endl;
00331         }
00332 
00333         // And then as a commitment
00334         CurrencyPool fromBudget = toTransfer - fromRecycled;
00335 
00336         if (debug) 
00337             cerr << "fromBudget = " << fromBudget << endl;
00338 
00339         // Take from parent recycled
00340         parentAccount.recycledOut += fromRecycled;
00341         parentAccount.balance -= fromRecycled;
00342         recycledIn += fromRecycled;
00343         balance += fromRecycled;
00344         
00345         // Give back to budget
00346         parentAccount.allocatedOut += fromBudget;
00347         parentAccount.balance -= fromBudget;
00348         budgetIncreases += fromBudget;
00349         balance += fromBudget;
00350 
00351         // Give to parent recycled
00352         parentAccount.recycledIn += toRecycled;
00353         parentAccount.balance += toRecycled;
00354         recycledOut += toRecycled;
00355         balance -= toRecycled;
00356 
00357         try {
00358             checkInvariants("exiting from setBalance");
00359             parentAccount.checkInvariants("parent check invariants");
00360         } catch (...) {
00361             cerr << "before: " << before << endl;
00362             cerr << "after:  " << *this << endl;
00363 
00364             cerr << "parent before: " << parentBefore << endl;
00365             cerr << "parent after:  " << parentAccount << endl;
00366             
00367             cerr << "newBalance = " << newBalance << endl;
00368             cerr << "balance = " << balance << endl;
00369             cerr << "requiredTotal = " << requiredTotal << endl;
00370             cerr << "requiredIn = " << requiredIn << endl;
00371             cerr << "requiredOut = " << requiredOut << endl;
00372             cerr << "toTransfer = " << toTransfer << endl;
00373             cerr << "parentRecycledAvail = " << parentRecycledAvail << endl;
00374             cerr << "fromRecycled = " << fromRecycled << endl;
00375 
00376             cerr << "fromBudget = " << fromBudget << endl;
00377             throw;
00378         }
00379 
00380         return balance;
00381     }
00382 
00385     CurrencyPool importSpend(const CurrencyPool & spend);
00386 
00387     void recuperateTo(Account & parentAccount)
00388     {
00389         CurrencyPool amount = balance.nonNegative();
00390 
00391         recycledOut += amount;
00392         balance -= amount;
00393 
00394         parentAccount.recycledIn += amount;
00395         parentAccount.balance += amount;
00396 
00397         checkInvariants("recuperateTo");
00398     }
00399 };
00400 
00401 
00402 /*****************************************************************************/
00403 /* SHADOW ACCOUNT                                                            */
00404 /*****************************************************************************/
00405 
00411 struct ShadowAccount {
00412     ShadowAccount()
00413         : attachedBids(0), detachedBids(0)
00414         {}
00415 
00416     // credit
00417     CurrencyPool netBudget;          
00418     CurrencyPool commitmentsRetired;
00419 
00420     // debit
00421     CurrencyPool commitmentsMade;
00422     CurrencyPool spent;
00423 
00424     CurrencyPool balance;  
00425 
00426     LineItems lineItems;  
00427 
00428     struct Commitment {
00429         Commitment(Amount amount, Date timestamp)
00430             : amount(amount), timestamp(timestamp)
00431         {
00432         }
00433 
00434         Amount amount;   
00435         Date timestamp;  
00436     };
00437 
00438     std::unordered_map<std::string, Commitment> commitments; 
00439 
00440     void checkInvariants() const
00441     {
00442         try {
00443             //ExcAssert(netBudget.isNonNegative());
00444             ExcAssert(commitmentsRetired.isNonNegative());
00445             ExcAssert(commitmentsMade.isNonNegative());
00446             ExcAssert(spent.isNonNegative());
00447             
00448             CurrencyPool credit = netBudget + commitmentsRetired;
00449             CurrencyPool debit = commitmentsMade + spent + balance;
00450             
00451             ExcAssertEqual(credit, debit);
00452         } catch (...) {
00453             using namespace std;
00454             cerr << "invariants failed:" << endl;
00455             cerr << *this << endl;
00456             throw;
00457         }
00458     }
00459 
00460     Json::Value toJson() const
00461     {
00462         checkInvariants();
00463 
00464         Json::Value result(Json::objectValue);
00465         result["md"]["objectType"] = "ShadowAccount";
00466         result["md"]["version"] = 1;
00467         result["netBudget"] = netBudget.toJson();
00468         result["commitmentsRetired"] = commitmentsRetired.toJson();
00469         result["commitmentsMade"] = commitmentsMade.toJson();
00470         result["spent"] = spent.toJson();
00471         result["lineItems"] = lineItems.toJson();
00472         result["balance"] = balance.toJson();
00473 
00474         ShadowAccount reparsed = fromJson(result);
00475         reparsed.checkInvariants();
00476         ExcAssertEqual(netBudget, reparsed.netBudget);
00477         ExcAssertEqual(spent, reparsed.spent);
00478         ExcAssertEqual(commitmentsRetired, reparsed.commitmentsRetired);
00479         ExcAssertEqual(commitmentsMade, reparsed.commitmentsMade);
00480         ExcAssertEqual(lineItems, reparsed.lineItems);
00481 
00482         return result;
00483     }
00484 
00485     static const ShadowAccount fromJson(const Json::Value & val)
00486     {
00487         ShadowAccount result;
00488         ExcAssertEqual(val["md"]["objectType"].asString(), "ShadowAccount");
00489         ExcAssertEqual(val["md"]["version"].asInt(), 1);
00490 
00491         result.netBudget = CurrencyPool::fromJson(val["netBudget"]);
00492         result.commitmentsRetired = CurrencyPool::fromJson(val["commitmentsRetired"]);
00493         result.commitmentsMade = CurrencyPool::fromJson(val["commitmentsMade"]);
00494         result.spent = CurrencyPool::fromJson(val["spent"]);
00495         result.balance = CurrencyPool::fromJson(val["balance"]);
00496         result.lineItems = LineItems::fromJson(val["lineItems"]);
00497 
00498         result.checkInvariants();
00499 
00500         return result;
00501     }
00502 
00503     /*************************************************************************/
00504     /* SPEND TRACKING                                                        */
00505     /*************************************************************************/
00506 
00507     void forceWinBid(Amount amountPaid,
00508                      const LineItems & lineItems)
00509     {
00510         commitDetachedBid(Amount(), amountPaid, lineItems);
00511     }
00512 
00514     void commitDetachedBid(Amount amountAuthorized,
00515                            Amount amountPaid,
00516                            const LineItems & lineItems)
00517     {
00518         checkInvariants();
00519         Amount amountUnspent = amountAuthorized - amountPaid;
00520         balance += amountUnspent;
00521         commitmentsRetired += amountAuthorized;
00522         spent += amountPaid;
00523         this->lineItems += lineItems;
00524         checkInvariants();
00525     }
00526 
00527 
00528     /*************************************************************************/
00529     /* SPEND AUTHORIZATION                                                   */
00530     /*************************************************************************/
00531 
00532     bool authorizeBid(const std::string & item,
00533                       Amount amount)
00534     {
00535         checkInvariants();
00536 
00537         if (!balance.hasAvailable(amount))
00538             return false;  // no budget balance
00539 
00540         attachBid(item, amount);
00541 
00542         balance -= amount;
00543         commitmentsMade += amount;
00544 
00545         checkInvariants();
00546 
00547         return true;
00548     }
00549     
00550     void commitBid(const std::string & item,
00551                    Amount amountPaid,
00552                    const LineItems & lineItems)
00553     {
00554         commitDetachedBid(detachBid(item), amountPaid, lineItems);
00555     }
00556 
00557     void cancelBid(const std::string & item)
00558     {
00559         commitDetachedBid(detachBid(item), Amount(), LineItems());
00560     }
00561     
00562     Amount detachBid(const std::string & item)
00563     {
00564         checkInvariants();
00565 
00566         auto cit = commitments.find(item);
00567         if (cit == commitments.end())
00568             throw ML::Exception("unknown commitment being committed");
00569 
00570         Amount amountAuthorized = cit->second.amount;
00571         commitments.erase(cit);
00572 
00573         checkInvariants();
00574 
00575         detachedBids++;
00576         
00577         return amountAuthorized;
00578     }
00579 
00580     void attachBid(const std::string & item,
00581                    Amount amount)
00582     {
00583         Date now = Date::now();
00584         auto c = commitments.insert(make_pair(item, Commitment(amount, now)));
00585         if (!c.second)
00586             throw ML::Exception("attempt to re-open commitment");
00587         attachedBids++;
00588     }
00589 
00590     /*************************************************************************/
00591     /* SYNCHRONIZATION                                                       */
00592     /*************************************************************************/
00593 
00594     const Account syncToMaster(Account & masterAccount) const
00595     {
00596         checkInvariants();
00597 
00598         masterAccount.checkInvariants();
00599 
00600         CurrencyPool newCommitmentsMade
00601             = commitmentsMade - masterAccount.commitmentsMade;
00602         CurrencyPool newCommitmentsRetired
00603             = commitmentsRetired - masterAccount.commitmentsRetired;
00604         CurrencyPool newSpend
00605             = spent - masterAccount.spent;
00606 
00607         ExcAssert(newCommitmentsMade.isNonNegative());
00608         ExcAssert(newCommitmentsRetired.isNonNegative());
00609         ExcAssert(newSpend.isNonNegative());
00610 
00611         masterAccount.commitmentsRetired = commitmentsRetired;
00612         masterAccount.commitmentsMade = commitmentsMade;
00613         masterAccount.spent = spent;
00614 
00615         masterAccount.balance
00616             += (newCommitmentsRetired - newCommitmentsMade - newSpend);
00617 
00618         masterAccount.lineItems = lineItems;
00619 
00620         masterAccount.checkInvariants("syncToMaster");
00621         checkInvariants();
00622 
00623         return masterAccount;
00624     }
00625 
00626     void syncFromMaster(const Account & masterAccount)
00627     {
00628         checkInvariants();
00629         masterAccount.checkInvariants();
00630 
00631         // net budget: balance assuming spent, commitments are zero
00632         netBudget = masterAccount.getNetBudget();
00633         balance = netBudget + commitmentsRetired
00634             - commitmentsMade - spent;
00635 
00636         checkInvariants();
00637     }
00638 
00648     void initializeAndMergeState(const Account & masterAccount)
00649     {
00650         // We have to tally up the fields from the master and the current
00651         // status.
00652 
00653         checkInvariants();
00654         masterAccount.checkInvariants();
00655 
00656         // net budget: balance assuming spent, commitments are zero
00657         netBudget = (masterAccount.budgetIncreases
00658                      - masterAccount.budgetDecreases
00659                      + masterAccount.recycledIn
00660                      - masterAccount.recycledOut
00661                      + masterAccount.adjustmentsIn
00662                      - masterAccount.adjustmentsOut
00663                      + masterAccount.allocatedIn
00664                      - masterAccount.allocatedOut);
00665         commitmentsMade += masterAccount.commitmentsMade;
00666         commitmentsRetired += masterAccount.commitmentsRetired;
00667         spent += masterAccount.spent;
00668         lineItems += masterAccount.lineItems;
00669 
00670         balance = netBudget + commitmentsRetired - commitmentsMade - spent;
00671 
00672         checkInvariants();
00673     }
00674 
00675     /* LOGGING */
00676     uint32_t attachedBids;
00677     uint32_t detachedBids;
00678     uint32_t lastExpiredCommitments;
00679 
00680     void logBidEvents(const Datacratic::EventRecorder & eventRecorder,
00681                       const std::string & accountKey);
00682 };
00683 
00684 
00685 /*****************************************************************************/
00686 /* ACCOUNT SUMMARY                                                           */
00687 /*****************************************************************************/
00688 
00691 struct AccountSummary {
00692     CurrencyPool budget;         
00693     CurrencyPool inFlight;       
00694     CurrencyPool spent;          
00695     CurrencyPool available;      
00696 
00697     Account account;
00698 
00699     void addChild(const std::string & name,
00700                   const AccountSummary & child,
00701                   bool addInSubaccounts)
00702     {
00703         if (addInSubaccounts)
00704             subAccounts[name] = child;
00705         inFlight += child.inFlight;
00706         spent += child.spent;
00707     }
00708 
00709     void dump(std::ostream & stream,
00710               int indent = 0,
00711               const std::string & name = "toplevel") const
00712     {
00713         stream << std::string(indent, ' ')
00714                << name
00715                << " b:" << budget
00716                << " s:" << spent
00717                << " i:" << inFlight
00718                << std::endl;
00719         for (const auto & sa: subAccounts) {
00720             sa.second.dump(stream, indent + 2, sa.first);
00721         }
00722     }
00723 
00724     Json::Value toJson(bool simplified = false) const
00725     {
00726         Json::Value result;
00727 
00728         result["md"]["objectType"]
00729             = simplified ? "AccountSimpleSummary" : "AccountSummary";
00730         result["md"]["version"] = 1;
00731         result["budget"] = budget.toJson();
00732         result["spent"] = spent.toJson();
00733         result["available"] = available.toJson();
00734         result["inFlight"] = inFlight.toJson();
00735         if (!simplified) {
00736             result["account"] = account.toJson();
00737             for (const auto & sa: subAccounts) {
00738                 result["subAccounts"][sa.first] = sa.second.toJson();
00739             }
00740         }
00741 
00742         return result;
00743     }
00744 
00745     static AccountSummary fromJson(const Json::Value & val)
00746     {
00747         AccountSummary result;
00748 
00749         ExcAssertEqual(val["md"]["objectType"].asString(), "AccountSummary");
00750         ExcAssertEqual(val["md"]["version"].asInt(), 1);
00751 
00752         result.budget = CurrencyPool::fromJson(val["budget"]);
00753         result.inFlight = CurrencyPool::fromJson(val["inFlight"]);
00754         result.spent = CurrencyPool::fromJson(val["spent"]);
00755         result.available = CurrencyPool::fromJson(val["available"]);
00756 
00757         result.account = Account::fromJson(val["account"]);
00758         auto & sa = val["subAccounts"];
00759         for (auto it = sa.begin(), end = sa.end();  it != end;  ++it) {
00760             result.subAccounts[it.memberName()]
00761                 = AccountSummary::fromJson(*it);
00762         }
00763 
00764         return result;
00765     }
00766 
00767     std::map<std::string, AccountSummary> subAccounts;
00768 };
00769 
00770 inline std::ostream &
00771 operator << (std::ostream & stream, const AccountSummary & summary)
00772 {
00773     summary.dump(stream);
00774     return stream;
00775 }
00776 
00777 /*****************************************************************************/
00778 /* ACCOUNTS                                                                  */
00779 /*****************************************************************************/
00780 
00781 struct Accounts {
00782     Accounts()
00783         : sessionStart(Datacratic::Date::now())
00784     {
00785     }
00786 
00787     Datacratic::Date sessionStart;
00788 
00789     struct AccountInfo: public Account {
00790         std::set<AccountKey> children;
00791 
00792         /* spend tracking across sessions */
00793         CurrencyPool initialSpent;
00794     };
00795 
00796     const Account createAccount(const AccountKey & account,
00797                                 AccountType type)
00798     {
00799         Guard guard(lock);
00800         if (account.empty())
00801             throw ML::Exception("can't create account with empty key");
00802         return ensureAccount(account, type);
00803     }
00804 
00805     void restoreAccount(const AccountKey & accountKey,
00806                         const Json::Value & jsonValue,
00807                         bool overwrite = false) {
00808         Guard guard(lock);
00809 
00810         // if (accounts.count(accountKey) != 0 and !overwrite) {
00811         //     throw ML::Exception("an account already exists with that name");
00812         // }
00813 
00814         Account validAccount = validAccount.fromJson(jsonValue);
00815         AccountInfo & newAccount = ensureAccount(accountKey, validAccount.type);
00816         newAccount.type = AT_SPEND;
00817         newAccount.type = validAccount.type;
00818         newAccount.budgetIncreases = validAccount.budgetIncreases;
00819         newAccount.budgetDecreases = validAccount.budgetDecreases;
00820         newAccount.spent = validAccount.spent;
00821         newAccount.recycledIn = validAccount.recycledIn;
00822         newAccount.recycledOut = validAccount.recycledOut;
00823         newAccount.allocatedIn = validAccount.allocatedIn;
00824         newAccount.allocatedOut = validAccount.allocatedOut;
00825         newAccount.commitmentsMade = validAccount.commitmentsMade;
00826         newAccount.commitmentsRetired = validAccount.commitmentsRetired;
00827         newAccount.adjustmentsIn = validAccount.adjustmentsIn;
00828         newAccount.adjustmentsOut = validAccount.adjustmentsOut;
00829         newAccount.balance = validAccount.balance;
00830         newAccount.lineItems = validAccount.lineItems;
00831         newAccount.adjustmentLineItems = validAccount.adjustmentLineItems;
00832     }
00833 
00834     const Account createBudgetAccount(const AccountKey & account)
00835     {
00836         Guard guard(lock);
00837         if (account.empty())
00838             throw ML::Exception("can't create account with empty key");
00839         return ensureAccount(account, AT_BUDGET);
00840     }
00841 
00842     const Account createSpendAccount(const AccountKey & account)
00843     {
00844         Guard guard(lock);
00845         if (account.size() < 2)
00846             throw ML::Exception("commitment account must have parent");
00847         return ensureAccount(account, AT_SPEND);
00848     }
00849 
00850     const AccountInfo getAccount(const AccountKey & account) const
00851     {
00852         Guard guard(lock);
00853         return getAccountImpl(account);
00854     }
00855 
00856     void checkInvariants() const
00857     {
00858         Guard guard(lock);
00859         for (auto & a: accounts) {
00860             a.second.checkInvariants();
00861         }
00862     }
00863 
00864     Json::Value toJson() const
00865     {
00866         Json::Value result(Json::objectValue);
00867 
00868         Guard guard(lock);
00869         for (auto & a: accounts) {
00870             result[a.first.toString()] = a.second.toJson();
00871         }
00872 
00873         return result;
00874     }
00875 
00876     static Accounts fromJson(const Json::Value & json);
00877 
00878     /*************************************************************************/
00879     /* BUDGET OPERATIONS                                                     */
00880     /*************************************************************************/
00881 
00882     /* These operations are assocated with putting money into the system. */
00883 
00884     const Account setBudget(const AccountKey & topLevelAccount,
00885                             const CurrencyPool & newBudget)
00886     {
00887         using namespace std;
00888         //cerr << "setBudget with newBudget " << newBudget << endl;
00889 
00890         Guard guard(lock);
00891         if (topLevelAccount.size() != 1)
00892             throw ML::Exception("can't setBudget except at top level");
00893         auto & a = ensureAccount(topLevelAccount, AT_BUDGET);
00894         a.setBudget(newBudget);
00895         return a;
00896     }
00897 
00904     const Account setBalance(const AccountKey & account,
00905                              CurrencyPool amount,
00906                              AccountType typeToCreate)
00907     {
00908         Guard guard(lock);
00909 
00910         if (typeToCreate != AT_NONE && !accounts.count(account)) {
00911             auto & a = ensureAccount(account, typeToCreate);
00912             a.setBalance(getParentAccount(account), amount);
00913             return a;
00914         }
00915         else {
00916             auto & a = getAccountImpl(account);
00917 
00918 #if 0
00919             using namespace std;
00920             if (a.type == AT_BUDGET)
00921                 cerr << Date::now()
00922                      << " setBalance " << account << " " << " from " << a.balance
00923                      << " to " << amount << endl;
00924 #endif
00925 
00926             a.setBalance(getParentAccount(account), amount);
00927             return a;
00928         }
00929     }
00930 
00931     const CurrencyPool getBalance(const AccountKey & account) const
00932     {
00933         Guard guard(lock);
00934         auto it = accounts.find(account);
00935         if (it == accounts.end())
00936             return CurrencyPool();
00937         return it->second.balance;
00938     }
00939 
00940 
00941     /*************************************************************************/
00942     /* TRANSFER OPERATIONS                                                   */
00943     /*************************************************************************/
00944 
00945     /* These operations are two-sided and involve transferring between a
00946        parent account and a child account.
00947     */
00948 
00949     void recuperate(const AccountKey & account)
00950     {
00951         Guard guard(lock);
00952         getAccountImpl(account).recuperateTo(getParentAccount(account));
00953     }
00954 
00955     AccountSummary getAccountSummary(const AccountKey & account,
00956                                      int maxDepth = -1) const
00957     {
00958         Guard guard(lock);
00959         return getAccountSummaryImpl(account, 0, maxDepth);
00960     }
00961 
00962     Json::Value
00963     getAccountSummariesJson(bool simplified = false, int maxDepth = -1)
00964         const
00965     {
00966         Guard guard(lock);
00967 
00968         Json::Value summaries;
00969 
00970         for (const auto & it: accounts) {
00971             const AccountKey & key = it.first;
00972             AccountSummary summary = getAccountSummaryImpl(key, 0, maxDepth);
00973             summaries[key.toString()] = summary.toJson(simplified);
00974         }
00975 
00976         return summaries;
00977     }
00978 
00979     const Account importSpend(const AccountKey & account,
00980                               const CurrencyPool & amount)
00981     {
00982         Guard guard(lock);
00983         auto & a = getAccountImpl(account);
00984         a.importSpend(amount);
00985         return a;
00986     }
00987                       
00988     /*************************************************************************/
00989     /* HIGH LEVEL OPERATIONS                                                 */
00990     /*************************************************************************/
00991 
00992     /* These are higher-level opertions that build on top of the others in
00993        order to make a given condition true.
00994     */
00995 
00996     /*************************************************************************/
00997     /* SYNCHRONIZATION OPERATIONS                                            */
00998     /*************************************************************************/
00999 
01000     const Account syncFromShadow(const AccountKey & account,
01001                                  const ShadowAccount & shadow)
01002     {
01003         Guard guard(lock);
01004 
01005         // In the case that an account was added and the banker crashed
01006         // before it could be written to persistent storage, we need to
01007         // create the empty account here.
01008         if (!accounts.count(account))
01009             return shadow.syncToMaster(ensureAccount(account, AT_SPEND));
01010         
01011         return shadow.syncToMaster(getAccountImpl(account));
01012     }
01013 
01014     /* "Out of sync" here means that the in-memory version of the relevant
01015        accounts is obsolete compared to the version stored in the Redis
01016        backend */
01017     void markAccountOutOfSync(const AccountKey & account)
01018     {
01019         Guard guard(lock);
01020 
01021         outOfSyncAccounts.insert(account);
01022     }
01023 
01024     bool isAccountOutOfSync(const AccountKey & account) const
01025     {
01026         Guard guard(lock);
01027         
01028         return (outOfSyncAccounts.count(account) > 0);
01029     }
01030 
01031 
01033     /* "Inconsistent" here means that there is a mismatch between the members
01034      * used in money transfers for a given Account and the corresponding
01035      * members in its subaccounts: allocatedOut and budgetIncreases,
01036      * recycledIn and recycedOut, ...
01037      */
01038     void ensureInterAccountConsistency();
01039     bool isAccountInconsistent(const AccountKey & account) const
01040     {
01041         Guard guard(lock);
01042         
01043         return (inconsistentAccounts.count(account) > 0);
01044     }
01045 
01046     /* Returns whether the budgetIncreases of subaccounts are consistent with
01047        the allocatedOut of the top-account, recursively.
01048        maxRecusion: -1 = infinity 
01049     */
01050     bool checkBudgetConsistency(const AccountKey & accountKey,
01051                                 int maxRecursion = -1) const;
01052 
01053     /* Returns the amounts in recycledIn and recycledOut that were transferred
01054      * strictly from and to the parent account. */
01055     void getRecycledUp(const AccountKey & accountKey,
01056                        CurrencyPool & recycledInUp,
01057                        CurrencyPool & recycledOutUp) const;
01058 
01059 private:
01060     friend class ShadowAccounts;
01061 
01062     typedef ML::Spinlock Lock;
01063     typedef std::unique_lock<Lock> Guard;
01064     mutable Lock lock;
01065 
01066     typedef std::map<AccountKey, AccountInfo> AccountMap;
01067     AccountMap accounts;
01068 
01069     typedef std::unordered_set<AccountKey> AccountSet;
01070     AccountSet outOfSyncAccounts;
01071     AccountSet inconsistentAccounts;
01072 
01073 public:
01074     std::vector<AccountKey>
01075     getAccountKeys(const AccountKey & prefix = AccountKey(),
01076                    int maxDepth = -1) const
01077     {
01078         Guard guard(lock);
01079 
01080         std::vector<AccountKey> result;
01081 
01082         for (auto it = accounts.lower_bound(prefix), end = accounts.end();
01083              it != accounts.end() && it->first.hasPrefix(prefix);  ++it) {
01084             if (maxDepth == -1 || it->first.size() <= maxDepth)
01085                 result.push_back(it->first);
01086         }
01087         return result;
01088     }
01089 
01090     void
01091     forEachAccount(const std::function<void (const AccountKey &,
01092                                              const Account &)>
01093                    & onAccount) const
01094     {
01095         Guard guard(lock);
01096         
01097         for (auto & a: accounts) {
01098             onAccount(a.first, a.second);
01099         }
01100     }
01101                         
01102     size_t size() const
01103     {
01104         Guard guard(lock);
01105         return accounts.size();
01106     }
01107 
01108     bool empty() const
01109     {
01110         Guard guard(lock);
01111         return accounts.empty();
01112     }
01113 
01115     Accounts getAccounts(const AccountKey & root, int maxDepth = 0)
01116     {
01117         Accounts result;
01118         Guard guard(lock);
01119 
01120         std::function<void (const AccountKey &, int, int)> doAccount
01121             = [&] (const AccountKey & key, int depth, int maxDepth)
01122             {
01123                 auto it = accounts.find(key);
01124                 if (it == accounts.end())
01125                     return;
01126                 result.ensureAccount(it->first, it->second.type) = it->second;
01127 
01128                 if (depth >= maxDepth)
01129                     return;
01130 
01131                 for (auto & k: it->second.children)
01132                     doAccount(k, depth + 1, maxDepth);
01133             };
01134               
01135         doAccount(root, 0, maxDepth);
01136 
01137         return result;
01138     }
01139 
01140 private:
01141 
01142     AccountInfo & ensureAccount(const AccountKey & accountKey,
01143                                 AccountType type)
01144     {
01145         ExcAssertGreaterEqual(accountKey.size(), 1);
01146 
01147         auto it = accounts.find(accountKey);
01148         if (it != accounts.end()) {
01149             ExcAssertEqual(it->second.type, type);
01150             return it->second;
01151         }
01152         else {
01153             if (accountKey.size() == 1) {
01154                 ExcAssertEqual(type, AT_BUDGET);
01155             }
01156             else {
01157                 AccountInfo & parent
01158                     = ensureAccount(accountKey.parent(), AT_BUDGET);
01159                 parent.children.insert(accountKey);
01160             }
01161 
01162             auto & result = accounts[accountKey];
01163             result.type = type;
01164             return result;
01165         }
01166     }
01167 
01168     AccountInfo & getAccountImpl(const AccountKey & account)
01169     {
01170         auto it = accounts.find(account);
01171         if (it == accounts.end())
01172             throw ML::Exception("couldn't get account");
01173         return it->second;
01174     }
01175 
01176     const AccountInfo & getAccountImpl(const AccountKey & account) const
01177     {
01178         auto it = accounts.find(account);
01179         if (it == accounts.end())
01180             throw ML::Exception("couldn't get account");
01181         return it->second;
01182     }
01183 
01184     Account & getParentAccount(const AccountKey & accountKey)
01185     {
01186         if (accountKey.size() < 2)
01187             throw ML::Exception("account has no parent");
01188 
01189         AccountKey parentKey = accountKey;
01190         parentKey.pop_back();
01191 
01192         Account & result = getAccountImpl(parentKey);
01193         ExcAssertEqual(result.type, AT_BUDGET);
01194         return result;
01195     }
01196 
01197     void forEachChildAccount(const AccountKey & account,
01198                              std::function<void (const AccountKey & key)> cb) const
01199     {
01200         auto & info = getAccountImpl(account);
01201         for (const AccountKey & ch: info.children)
01202             cb(ch);
01203     }
01204 
01205     AccountSummary getAccountSummaryImpl(const AccountKey & account,
01206                                          int depth, int maxDepth) const
01207     {
01208         AccountSummary result;
01209 
01210         const Account & a = getAccountImpl(account);
01211 
01212         result.account = a;
01213         result.spent = a.spent;
01214         result.budget = a.getBudget();
01215         result.inFlight = a.commitmentsMade - a.commitmentsRetired;
01216 
01217         auto doChildAccount = [&] (const AccountKey & key) {
01218             auto childSummary = getAccountSummaryImpl(key, depth + 1,
01219                                                       maxDepth);
01220             result.addChild(key.back(), childSummary,
01221                             maxDepth == -1 || depth < maxDepth);
01222         };
01223         forEachChildAccount(account, doChildAccount);
01224 
01225         result.available = result.budget - result.spent - result.inFlight;
01226         
01227         return result;
01228     }
01229 
01230     bool checkBudgetConsistencyImpl(const AccountKey & accountKey,
01231                                     int maxRecursion, int currentLevel) const;
01232 };
01233 
01234 
01235 /*****************************************************************************/
01236 /* SHADOW ACCOUNTS                                                           */
01237 /*****************************************************************************/
01238 
01239 struct ShadowAccounts {
01244     std::function<void (AccountKey)> onNewAccount;
01245     
01246     const ShadowAccount activateAccount(const AccountKey & account)
01247     {
01248         Guard guard(lock);
01249         return getAccountImpl(account);
01250     }
01251 
01252     const ShadowAccount syncFromMaster(const AccountKey & account,
01253                                        const Account & master)
01254     {
01255         Guard guard(lock);
01256         auto & a = getAccountImpl(account);
01257         ExcAssert(!a.uninitialized);
01258         a.syncFromMaster(master);
01259         return a;
01260     }
01261 
01265     const ShadowAccount
01266     initializeAndMergeState(const AccountKey & account,
01267                             const Account & master)
01268     {
01269         Guard guard(lock);
01270         auto & a = getAccountImpl(account);
01271         ExcAssert(a.uninitialized);
01272         a.initializeAndMergeState(master);
01273         a.uninitialized = false;
01274         return a;
01275     }
01276 
01277     void checkInvariants() const
01278     {
01279         Guard guard(lock);
01280         for (auto & a: accounts) {
01281             a.second.checkInvariants();
01282         }
01283     }
01284 
01285     const ShadowAccount getAccount(const AccountKey & accountKey) const
01286     {
01287         Guard guard(lock);
01288         return getAccountImpl(accountKey);
01289     }
01290 
01291     bool accountExists(const AccountKey & accountKey) const
01292     {
01293         Guard guard(lock);
01294         return accounts.count(accountKey);
01295     }
01296 
01297     bool createAccountAtomic(const AccountKey & accountKey)
01298     {
01299         Guard guard(lock);
01300 
01301         AccountEntry & account = getAccountImpl(accountKey, false /* call onCreate */);
01302         bool result = account.first;
01303 
01304         // record that this account creation is requested for the first time
01305         account.first = false;
01306         return result;
01307 
01308     }
01309 
01310     /*************************************************************************/
01311     /* SYNCHRONIZATION                                                       */
01312     /*************************************************************************/
01313 
01314     void syncTo(Accounts & master) const
01315     {
01316         Guard guard1(lock);
01317         Guard guard2(master.lock);
01318 
01319         for (auto & a: accounts)
01320             a.second.syncToMaster(master.getAccountImpl(a.first));
01321     }
01322 
01323     void syncFrom(const Accounts & master)
01324     {
01325         Guard guard1(lock);
01326         Guard guard2(master.lock);
01327 
01328         for (auto & a: accounts) {
01329             a.second.syncFromMaster(master.getAccountImpl(a.first));
01330             if (master.outOfSyncAccounts.count(a.first) > 0) {
01331                 outOfSyncAccounts.insert(a.first);
01332             }
01333         }
01334     }
01335 
01336     void sync(Accounts & master)
01337     {
01338         Guard guard1(lock);
01339         Guard guard2(master.lock);
01340 
01341         for (auto & a: accounts) {
01342             a.second.syncToMaster(master.getAccountImpl(a.first));
01343             a.second.syncFromMaster(master.getAccountImpl(a.first));
01344         }
01345     }
01346 
01347     bool isInitialized(const AccountKey & accountKey) const
01348     {
01349         Guard guard(lock);
01350         return !getAccountImpl(accountKey).uninitialized;
01351     }
01352 
01353     /*************************************************************************/
01354     /* BID OPERATIONS                                                        */
01355     /*************************************************************************/
01356 
01357     bool authorizeBid(const AccountKey & accountKey,
01358                       const std::string & item,
01359                       Amount amount)
01360     {
01361         Guard guard(lock);
01362         return (outOfSyncAccounts.count(accountKey) == 0
01363                 && getAccountImpl(accountKey).authorizeBid(item, amount));
01364     }
01365     
01366     void commitBid(const AccountKey & accountKey,
01367                    const std::string & item,
01368                    Amount amountPaid,
01369                    const LineItems & lineItems)
01370     {
01371         Guard guard(lock);
01372         return getAccountImpl(accountKey).commitBid(item, amountPaid, lineItems);
01373     }
01374 
01375     void cancelBid(const AccountKey & accountKey,
01376                    const std::string & item)
01377     {
01378         Guard guard(lock);
01379         return getAccountImpl(accountKey).cancelBid(item);
01380     }
01381     
01382     void forceWinBid(const AccountKey & accountKey,
01383                      Amount amountPaid,
01384                      const LineItems & lineItems)
01385     {
01386         Guard guard(lock);
01387         return getAccountImpl(accountKey).forceWinBid(amountPaid, lineItems);
01388     }
01389 
01391     void commitDetachedBid(const AccountKey & accountKey,
01392                            Amount amountAuthorized,
01393                            Amount amountPaid,
01394                            const LineItems & lineItems)
01395     {
01396         Guard guard(lock);
01397         return getAccountImpl(accountKey)
01398             .commitDetachedBid(amountAuthorized, amountPaid, lineItems);
01399     }
01400 
01401     Amount detachBid(const AccountKey & accountKey,
01402                      const std::string & item)
01403     {
01404         Guard guard(lock);
01405         return getAccountImpl(accountKey).detachBid(item);
01406     }
01407 
01408     void attachBid(const AccountKey & accountKey,
01409                    const std::string & item,
01410                    Amount amountAuthorized)
01411     {
01412         Guard guard(lock);
01413         getAccountImpl(accountKey).attachBid(item, amountAuthorized);
01414     }
01415 
01416     void logBidEvents(const Datacratic::EventRecorder & eventRecorder);
01417 
01418 private:
01419 
01420     struct AccountEntry : public ShadowAccount {
01421         AccountEntry(bool uninitialized = true, bool first = true)
01422             : uninitialized(uninitialized), first(first)
01423         {
01424         }
01425 
01436         bool uninitialized;
01437         bool first;
01438     };
01439 
01440     AccountEntry & getAccountImpl(const AccountKey & account,
01441                                   bool callOnNewAccount = true)
01442     {
01443         auto it = accounts.find(account);
01444         if (it == accounts.end()) {
01445             if (callOnNewAccount && onNewAccount)
01446                 onNewAccount(account);
01447             it = accounts.insert(std::make_pair(account, AccountEntry()))
01448                 .first;
01449         }
01450         return it->second;
01451     }
01452 
01453     const AccountEntry & getAccountImpl(const AccountKey & account) const
01454     {
01455         auto it = accounts.find(account);
01456         if (it == accounts.end())
01457             throw ML::Exception("getting unknown account " + account.toString());
01458         return it->second;
01459     }
01460 
01461     typedef ML::Spinlock Lock;
01462     typedef std::unique_lock<Lock> Guard;
01463     mutable Lock lock;
01464 
01465     typedef std::map<AccountKey, AccountEntry> AccountMap;
01466     AccountMap accounts;
01467 
01468     typedef std::unordered_set<AccountKey> AccountSet;
01469     AccountSet outOfSyncAccounts;
01470 
01471 public:
01472     std::vector<AccountKey>
01473     getAccountKeys(const AccountKey & prefix = AccountKey()) const
01474     {
01475         Guard guard(lock);
01476 
01477         std::vector<AccountKey> result;
01478 
01479         for (auto it = accounts.lower_bound(prefix), end = accounts.end();
01480              it != accounts.end() && it->first.hasPrefix(prefix);  ++it) {
01481             result.push_back(it->first);
01482         }
01483         return result;
01484     }
01485 
01486     void
01487     forEachAccount(const std::function<void (const AccountKey &,
01488                                              const ShadowAccount &)> &
01489                    onAccount) const
01490     {
01491         Guard guard(lock);
01492         
01493         for (auto & a: accounts) {
01494             onAccount(a.first, a.second);
01495         }
01496     }
01497 
01498     void
01499     forEachInitializedAccount(const std::function<void (const AccountKey &,
01500                                                         const ShadowAccount &)> & onAccount)
01501     {
01502         Guard guard(lock);
01503         
01504         for (auto & a: accounts) {
01505             if (a.second.uninitialized)
01506                 continue;
01507             onAccount(a.first, a.second);
01508         }
01509     }
01510 
01511     size_t size() const
01512     {
01513         Guard guard(lock);
01514         return accounts.size();
01515     }
01516 
01517     bool empty() const
01518     {
01519         Guard guard(lock);
01520         return accounts.empty();
01521     }
01522 };
01523 
01524 } // namespace RTBKIT
01525 
 All Classes Namespaces Functions Variables Typedefs Enumerations Enumerator