xrpld
Loading...
Searching...
No Matches
libxrpl/server/InfoSub.cpp
1#include <xrpl/server/InfoSub.h>
2
3#include <xrpl/basics/Log.h>
4#include <xrpl/beast/utility/Journal.h>
5#include <xrpl/beast/utility/instrumentation.h>
6#include <xrpl/protocol/AccountID.h>
7#include <xrpl/protocol/Book.h>
8#include <xrpl/resource/Consumer.h>
9
10#include <cstddef>
11#include <cstdint>
12#include <exception>
13#include <memory>
14#include <mutex>
15#include <utility>
16
17namespace xrpl {
18
19namespace {
20
21// Wraps a Source teardown call so that an exception from one cleanup
22// step does not prevent the subsequent steps from running. Source methods
23// acquire a lock and can throw std::system_error; a throw out of ~InfoSub
24// during stack unwinding would terminate the process. Failures are
25// reported through the Source's Journal so they reach the configured log
26// sinks; JLOG itself cannot throw, so the noexcept guarantee holds.
27template <typename F>
28void
29safeUnsub(std::uint64_t seq, F&& f, beast::Journal j) noexcept
30{
31 try
32 {
33 f();
34 }
35 catch (std::exception const& e)
36 {
37 JLOG(j.warn()) << "~InfoSub[seq=" << seq << "]: cleanup step failed: " << e.what();
38 }
39 catch (...)
40 {
41 JLOG(j.warn()) << "~InfoSub[seq=" << seq << "]: cleanup step failed: unknown exception";
42 }
43}
44
45} // namespace
46
47// This is the primary interface into the "client" portion of the program.
48// Code that wants to do normal operations on the network such as
49// creating and monitoring accounts, creating transactions, and so on
50// should use this interface. The RPC code will primarily be a light wrapper
51// over this code.
52
53// Eventually, it will check the node's operating mode (synced, unsynced,
54// etcetera) and defer to the correct means of processing. The current
55// code assumes this node is synced (and will continue to do so until
56// there's a functional network.
57
59{
60}
61
63 : consumer_(consumer), source_(source), seq_(assignId())
64{
65}
66
68{
69 // Stream unsubscribes are O(1): each erases this connection's single seq_
70 // from one stream map, so they are cheap enough to run inline on the
71 // disconnect thread.
72 // Each Source teardown call below acquires a server-side lock and
73 // can throw. Wrap each independent call so partial failure does not
74 // skip the remaining teardown steps.
75
76 auto const& j = source_.journal();
77
78 safeUnsub(seq_, [&] { source_.unsubTransactions(seq_); }, j);
79 safeUnsub(seq_, [&] { source_.unsubRTTransactions(seq_); }, j);
80 safeUnsub(seq_, [&] { source_.unsubLedger(seq_); }, j);
81 safeUnsub(seq_, [&] { source_.unsubManifests(seq_); }, j);
82 safeUnsub(seq_, [&] { source_.unsubServer(seq_); }, j);
83 safeUnsub(seq_, [&] { source_.unsubValidations(seq_); }, j);
84 safeUnsub(seq_, [&] { source_.unsubPeerStatus(seq_); }, j);
85 safeUnsub(seq_, [&] { source_.unsubConsensus(seq_); }, j);
86
87 // Book subscriptions are torn down inline here, keyed on seq_, rather than
88 // through the chunked account cleanup below. The book set is not capped, so
89 // it can be large; but each unsubBookInternal takes bookLock_ for a single
90 // O(1) erase and releases it, so even a large set never holds a lock across
91 // the whole loop - a competing book publish can interleave between erases.
92 // The disconnect thread still does O(N) brief acquisitions. Use the internal
93 // variant so it does not write back to bookSubscriptions_ on this
94 // partially-destroyed object.
95 for (auto const& book : bookSubscriptions_)
96 {
97 safeUnsub(seq_, [&] { source_.unsubBookInternal(seq_, book); }, j);
98 }
99
100 // Hand the account sets off (by move) to the Source for a chunked,
101 // off-thread teardown keyed on seq_, instead of erasing them inline here.
102 // This keeps the destructor from holding the account lock across a large
103 // erase loop. The job never references this object, which is being
104 // destroyed.
105 //
106 // Moving the sets without holding lock_ is safe: the destructor runs only
107 // when the last shared_ptr to this InfoSub is released, so by the
108 // shared_ptr contract no other thread holds a reference. Subscription maps
109 // store weak_ptrs, so a concurrent publisher must weak_ptr::lock() first;
110 // that succeeds only while a strong reference exists, which cannot overlap
111 // with destruction. No other thread can observe the moved-from sets.
112 //
113 // Wrapped like the steps above: scheduleAccountCleanup enqueues a JobQueue
114 // task, which allocates and locks and so can throw. A throw out of this
115 // noexcept destructor would terminate the process. Skipping the cleanup on
116 // throw is harmless: the account/rt maps hold weak_ptrs that the next
117 // publish prunes once this InfoSub is gone, and any history paging job
118 // self-terminates when its weak sink can no longer be locked.
119 safeUnsub(
120 seq_,
121 [&] {
122 source_.scheduleAccountCleanup(
123 seq_,
124 std::move(realTimeSubscriptions_),
125 std::move(normalSubscriptions_),
127 },
128 j);
129}
130
133{
134 return consumer_;
135}
136
139{
140 return seq_;
141}
142
143void
147
150{
151 // Hold lock_ for the whole read so the three sets cannot be mutated
152 // mid-count by a concurrent (un)subscribe on this connection.
153 std::scoped_lock const sl(lock_);
154
155 // Combined tally the per-connection cap is enforced against.
156 return normalSubscriptions_.size() + realTimeSubscriptions_.size() +
158}
159
160bool
162 hash_set<AccountID> const& proposedAccounts,
163 hash_set<AccountID> const& normalAccounts,
164 std::size_t cap)
165{
166 // One lock hold covers the count, the check and the insert.
167 std::scoped_lock const sl(lock_);
168
169 // Entries not already tracked; re-subscribing held accounts is not charged.
170 auto const countNew = [](hash_set<AccountID> const& requested,
171 hash_set<AccountID> const& existing) {
172 std::size_t fresh = 0;
173 for (auto const& account : requested)
174 {
175 if (!existing.contains(account))
176 ++fresh;
177 }
178 return fresh;
179 };
180
181 std::size_t const additional = countNew(proposedAccounts, realTimeSubscriptions_) +
182 countNew(normalAccounts, normalSubscriptions_);
183
184 std::size_t const current = normalSubscriptions_.size() + realTimeSubscriptions_.size() +
186
187 if (exceedsSubscriptionCap(current, additional, cap))
188 return false;
189
190 realTimeSubscriptions_.insert(proposedAccounts.begin(), proposedAccounts.end());
191 normalSubscriptions_.insert(normalAccounts.begin(), normalAccounts.end());
192 return true;
193}
194
195void
197{
198 std::scoped_lock const sl(lock_);
199
200 if (rt)
201 {
202 realTimeSubscriptions_.insert(account);
203 }
204 else
205 {
206 normalSubscriptions_.insert(account);
207 }
208}
209
210void
212{
213 std::scoped_lock const sl(lock_);
214
215 if (rt)
216 {
217 realTimeSubscriptions_.erase(account);
218 }
219 else
220 {
221 normalSubscriptions_.erase(account);
222 }
223}
224
225bool
227{
228 std::scoped_lock const sl(lock_);
229 return accountHistorySubscriptions_.insert(account).second;
230}
231
232void
234{
235 std::scoped_lock const sl(lock_);
236 accountHistorySubscriptions_.erase(account);
237}
238
239bool
241{
242 std::scoped_lock const sl(lock_);
243 return accountHistorySubscriptions_.contains(account);
244}
245
246void
248{
249 std::scoped_lock const sl(lock_);
250 bookSubscriptions_.insert(book);
251}
252
253void
255{
256 std::scoped_lock const sl(lock_);
257 bookSubscriptions_.erase(book);
258}
259
260void
262{
263 request_.reset();
264}
265
266void
271
274{
275 return request_;
276}
277
278void
279InfoSub::setApiVersion(unsigned int apiVersion)
280{
281 apiVersion_ = apiVersion;
282}
283
284unsigned int
286{
287 XRPL_ASSERT(apiVersion_ > 0, "xrpl::InfoSub::getApiVersion : valid API version");
288 return apiVersion_;
289}
290
291} // namespace xrpl
T begin(T... args)
Specifies an order book.
Definition Book.h:28
Abstracts the source of subscription data.
Definition InfoSub.h:106
void setRequest(std::shared_ptr< InfoSubRequest > const &req)
void insertBookSubscription(Book const &book)
Record that this subscriber is following book.
InfoSub(Source &source)
Consumer consumer_
Definition InfoSub.h:422
bool insertSubAccountHistory(AccountID const &account)
bool tryReserveAccountSubscriptions(hash_set< AccountID > const &proposedAccounts, hash_set< AccountID > const &normalAccounts, std::size_t cap)
Enforce the cap and reserve a request's net-new accounts, atomically.
std::size_t totalSubscriptionCount() const
Return the number of subscriptions currently tracked on this connection.
void setApiVersion(unsigned int apiVersion)
static int assignId()
Definition InfoSub.h:433
std::uint64_t getSeq() const
resource::Consumer Consumer
Definition InfoSub.h:99
void insertSubAccountInfo(AccountID const &account, bool rt)
void deleteBookSubscription(Book const &book)
Stop tracking book for this subscriber.
bool hasAccountHistorySubscription(AccountID const &account) const
Whether this connection already tracks an account-history for account.
Source & source_
Definition InfoSub.h:423
hash_set< AccountID > accountHistorySubscriptions_
Definition InfoSub.h:428
hash_set< AccountID > normalSubscriptions_
Definition InfoSub.h:425
hash_set< Book > bookSubscriptions_
Definition InfoSub.h:429
hash_set< AccountID > realTimeSubscriptions_
Definition InfoSub.h:424
void deleteSubAccountInfo(AccountID const &account, bool rt)
void deleteSubAccountHistory(AccountID const &account)
unsigned int getApiVersion() const noexcept
std::uint64_t seq_
Definition InfoSub.h:427
std::mutex lock_
Definition InfoSub.h:419
unsigned int apiVersion_
Definition InfoSub.h:430
std::shared_ptr< InfoSubRequest > const & getRequest()
std::shared_ptr< InfoSubRequest > request_
Definition InfoSub.h:426
An endpoint that consumes resources.
Definition Consumer.h:20
T end(T... args)
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
std::unordered_set< Value, Hash, Pred, Allocator > hash_set
BaseUInt< 160, detail::AccountIDTag > AccountID
A 160-bit unsigned that uniquely identifies an account.
Definition AccountID.h:34
constexpr bool exceedsSubscriptionCap(std::size_t current, std::size_t additional, std::size_t cap=kMaxSubscriptionsPerConnection)
Whether adding additional subscriptions to a connection already holding current would exceed the cap.
Definition InfoSub.h:51
T what(T... args)