LLDB  mainline
Listener.cpp
Go to the documentation of this file.
1 //===-- Listener.cpp ------------------------------------------------------===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8 
10 
13 #include "lldb/Utility/Event.h"
14 #include "lldb/Utility/Log.h"
15 #include "lldb/Utility/Logging.h"
16 
17 #include "llvm/ADT/Optional.h"
18 
19 #include <algorithm>
20 #include <memory>
21 #include <utility>
22 
23 using namespace lldb;
24 using namespace lldb_private;
25 
26 namespace {
27 class BroadcasterManagerWPMatcher {
28 public:
29  BroadcasterManagerWPMatcher(BroadcasterManagerSP manager_sp)
30  : m_manager_sp(std::move(manager_sp)) {}
31  bool operator()(const BroadcasterManagerWP &input_wp) const {
32  BroadcasterManagerSP input_sp = input_wp.lock();
33  return (input_sp && input_sp == m_manager_sp);
34  }
35 
36  BroadcasterManagerSP m_manager_sp;
37 };
38 } // anonymous namespace
39 
40 Listener::Listener(const char *name)
41  : m_name(name), m_broadcasters(), m_broadcasters_mutex(), m_events(),
42  m_events_mutex() {
44  if (log != nullptr)
45  LLDB_LOGF(log, "%p Listener::Listener('%s')", static_cast<void *>(this),
46  m_name.c_str());
47 }
48 
51 
52  Clear();
53 
54  LLDB_LOGF(log, "%p Listener::%s('%s')", static_cast<void *>(this),
55  __FUNCTION__, m_name.c_str());
56 }
57 
60  std::lock_guard<std::recursive_mutex> broadcasters_guard(
62  broadcaster_collection::iterator pos, end = m_broadcasters.end();
63  for (pos = m_broadcasters.begin(); pos != end; ++pos) {
64  Broadcaster::BroadcasterImplSP broadcaster_sp(pos->first.lock());
65  if (broadcaster_sp)
66  broadcaster_sp->RemoveListener(this, pos->second.event_mask);
67  }
68  m_broadcasters.clear();
69 
70  std::lock_guard<std::mutex> events_guard(m_events_mutex);
71  m_events.clear();
72  size_t num_managers = m_broadcaster_managers.size();
73 
74  for (size_t i = 0; i < num_managers; i++) {
75  BroadcasterManagerSP manager_sp(m_broadcaster_managers[i].lock());
76  if (manager_sp)
77  manager_sp->RemoveListener(this);
78  }
79 
80  LLDB_LOGF(log, "%p Listener::%s('%s')", static_cast<void *>(this),
81  __FUNCTION__, m_name.c_str());
82 }
83 
85  uint32_t event_mask) {
86  if (broadcaster) {
87  // Scope for "locker"
88  // Tell the broadcaster to add this object as a listener
89  {
90  std::lock_guard<std::recursive_mutex> broadcasters_guard(
92  Broadcaster::BroadcasterImplWP impl_wp(broadcaster->GetBroadcasterImpl());
93  m_broadcasters.insert(
94  std::make_pair(impl_wp, BroadcasterInfo(event_mask)));
95  }
96 
97  uint32_t acquired_mask =
98  broadcaster->AddListener(this->shared_from_this(), event_mask);
99 
101  if (log != nullptr)
102  LLDB_LOGF(log,
103  "%p Listener::StartListeningForEvents (broadcaster = %p, "
104  "mask = 0x%8.8x) acquired_mask = 0x%8.8x for %s",
105  static_cast<void *>(this), static_cast<void *>(broadcaster),
106  event_mask, acquired_mask, m_name.c_str());
107 
108  return acquired_mask;
109  }
110  return 0;
111 }
112 
114  uint32_t event_mask,
115  HandleBroadcastCallback callback,
116  void *callback_user_data) {
117  if (broadcaster) {
118  // Scope for "locker"
119  // Tell the broadcaster to add this object as a listener
120  {
121  std::lock_guard<std::recursive_mutex> broadcasters_guard(
123  Broadcaster::BroadcasterImplWP impl_wp(broadcaster->GetBroadcasterImpl());
124  m_broadcasters.insert(std::make_pair(
125  impl_wp, BroadcasterInfo(event_mask, callback, callback_user_data)));
126  }
127 
128  uint32_t acquired_mask =
129  broadcaster->AddListener(this->shared_from_this(), event_mask);
130 
132  if (log != nullptr) {
133  void **pointer = reinterpret_cast<void **>(&callback);
134  LLDB_LOGF(log,
135  "%p Listener::StartListeningForEvents (broadcaster = %p, "
136  "mask = 0x%8.8x, callback = %p, user_data = %p) "
137  "acquired_mask = 0x%8.8x for %s",
138  static_cast<void *>(this), static_cast<void *>(broadcaster),
139  event_mask, *pointer, static_cast<void *>(callback_user_data),
140  acquired_mask, m_name.c_str());
141  }
142 
143  return acquired_mask;
144  }
145  return 0;
146 }
147 
149  uint32_t event_mask) {
150  if (broadcaster) {
151  // Scope for "locker"
152  {
153  std::lock_guard<std::recursive_mutex> broadcasters_guard(
155  m_broadcasters.erase(broadcaster->GetBroadcasterImpl());
156  }
157  // Remove the broadcaster from our set of broadcasters
158  return broadcaster->RemoveListener(this->shared_from_this(), event_mask);
159  }
160 
161  return false;
162 }
163 
164 // Called when a Broadcaster is in its destructor. We need to remove all
165 // knowledge of this broadcaster and any events that it may have queued up
167  // Scope for "broadcasters_locker"
168  {
169  std::lock_guard<std::recursive_mutex> broadcasters_guard(
171  m_broadcasters.erase(broadcaster->GetBroadcasterImpl());
172  }
173 
174  // Scope for "event_locker"
175  {
176  std::lock_guard<std::mutex> events_guard(m_events_mutex);
177  // Remove all events for this broadcaster object.
178  event_collection::iterator pos = m_events.begin();
179  while (pos != m_events.end()) {
180  if ((*pos)->GetBroadcaster() == broadcaster)
181  pos = m_events.erase(pos);
182  else
183  ++pos;
184  }
185  }
186 }
187 
188 void Listener::BroadcasterManagerWillDestruct(BroadcasterManagerSP manager_sp) {
189  // Just need to remove this broadcast manager from the list of managers:
190  broadcaster_manager_collection::iterator iter,
191  end_iter = m_broadcaster_managers.end();
192  BroadcasterManagerWP manager_wp;
193 
194  BroadcasterManagerWPMatcher matcher(std::move(manager_sp));
195  iter = std::find_if<broadcaster_manager_collection::iterator,
196  BroadcasterManagerWPMatcher>(
197  m_broadcaster_managers.begin(), end_iter, matcher);
198  if (iter != end_iter)
199  m_broadcaster_managers.erase(iter);
200 }
201 
202 void Listener::AddEvent(EventSP &event_sp) {
204  if (log != nullptr)
205  LLDB_LOGF(log, "%p Listener('%s')::AddEvent (event_sp = {%p})",
206  static_cast<void *>(this), m_name.c_str(),
207  static_cast<void *>(event_sp.get()));
208 
209  std::lock_guard<std::mutex> guard(m_events_mutex);
210  m_events.push_back(event_sp);
211  m_events_condition.notify_all();
212 }
213 
215 public:
217  : m_broadcaster(broadcaster) {}
218 
219  bool operator()(const EventSP &event_sp) const {
220  return event_sp->BroadcasterIs(m_broadcaster);
221  }
222 
223 private:
225 };
226 
228 public:
229  EventMatcher(Broadcaster *broadcaster, const ConstString *broadcaster_names,
230  uint32_t num_broadcaster_names, uint32_t event_type_mask)
231  : m_broadcaster(broadcaster), m_broadcaster_names(broadcaster_names),
232  m_num_broadcaster_names(num_broadcaster_names),
233  m_event_type_mask(event_type_mask) {}
234 
235  bool operator()(const EventSP &event_sp) const {
236  if (m_broadcaster && !event_sp->BroadcasterIs(m_broadcaster))
237  return false;
238 
239  if (m_broadcaster_names) {
240  bool found_source = false;
241  ConstString event_broadcaster_name =
242  event_sp->GetBroadcaster()->GetBroadcasterName();
243  for (uint32_t i = 0; i < m_num_broadcaster_names; ++i) {
244  if (m_broadcaster_names[i] == event_broadcaster_name) {
245  found_source = true;
246  break;
247  }
248  }
249  if (!found_source)
250  return false;
251  }
252 
253  return m_event_type_mask == 0 || m_event_type_mask & event_sp->GetType();
254  }
255 
256 private:
261 };
262 
264  std::unique_lock<std::mutex> &lock,
265  Broadcaster *broadcaster, // nullptr for any broadcaster
266  const ConstString *broadcaster_names, // nullptr for any event
267  uint32_t num_broadcaster_names, uint32_t event_type_mask, EventSP &event_sp,
268  bool remove) {
269  // NOTE: callers of this function must lock m_events_mutex using a
270  // Mutex::Locker
271  // and pass the locker as the first argument. m_events_mutex is no longer
272  // recursive.
274 
275  if (m_events.empty())
276  return false;
277 
278  Listener::event_collection::iterator pos = m_events.end();
279 
280  if (broadcaster == nullptr && broadcaster_names == nullptr &&
281  event_type_mask == 0) {
282  pos = m_events.begin();
283  } else {
284  pos = std::find_if(m_events.begin(), m_events.end(),
285  EventMatcher(broadcaster, broadcaster_names,
286  num_broadcaster_names, event_type_mask));
287  }
288 
289  if (pos != m_events.end()) {
290  event_sp = *pos;
291 
292  if (log != nullptr)
293  LLDB_LOGF(log,
294  "%p '%s' Listener::FindNextEventInternal(broadcaster=%p, "
295  "broadcaster_names=%p[%u], event_type_mask=0x%8.8x, "
296  "remove=%i) event %p",
297  static_cast<void *>(this), GetName(),
298  static_cast<void *>(broadcaster),
299  static_cast<const void *>(broadcaster_names),
300  num_broadcaster_names, event_type_mask, remove,
301  static_cast<void *>(event_sp.get()));
302 
303  if (remove) {
304  m_events.erase(pos);
305  // Unlock the event queue here. We've removed this event and are about
306  // to return it so it should be okay to get the next event off the queue
307  // here - and it might be useful to do that in the "DoOnRemoval".
308  lock.unlock();
309  event_sp->DoOnRemoval();
310  }
311  return true;
312  }
313 
314  event_sp.reset();
315  return false;
316 }
317 
319  std::unique_lock<std::mutex> guard(m_events_mutex);
320  EventSP event_sp;
321  if (FindNextEventInternal(guard, nullptr, nullptr, 0, 0, event_sp, false))
322  return event_sp.get();
323  return nullptr;
324 }
325 
327  std::unique_lock<std::mutex> guard(m_events_mutex);
328  EventSP event_sp;
329  if (FindNextEventInternal(guard, broadcaster, nullptr, 0, 0, event_sp, false))
330  return event_sp.get();
331  return nullptr;
332 }
333 
334 Event *
336  uint32_t event_type_mask) {
337  std::unique_lock<std::mutex> guard(m_events_mutex);
338  EventSP event_sp;
339  if (FindNextEventInternal(guard, broadcaster, nullptr, 0, event_type_mask,
340  event_sp, false))
341  return event_sp.get();
342  return nullptr;
343 }
344 
346  const Timeout<std::micro> &timeout,
347  Broadcaster *broadcaster, // nullptr for any broadcaster
348  const ConstString *broadcaster_names, // nullptr for any event
349  uint32_t num_broadcaster_names, uint32_t event_type_mask,
350  EventSP &event_sp) {
352  LLDB_LOG(log, "this = {0}, timeout = {1} for {2}", this, timeout, m_name);
353 
354  std::unique_lock<std::mutex> lock(m_events_mutex);
355 
356  while (true) {
357  if (FindNextEventInternal(lock, broadcaster, broadcaster_names,
358  num_broadcaster_names, event_type_mask, event_sp,
359  true)) {
360  return true;
361  } else {
362  std::cv_status result = std::cv_status::no_timeout;
363  if (!timeout)
364  m_events_condition.wait(lock);
365  else
366  result = m_events_condition.wait_for(lock, *timeout);
367 
368  if (result == std::cv_status::timeout) {
370  LLDB_LOGF(log, "%p Listener::GetEventInternal() timed out for %s",
371  static_cast<void *>(this), m_name.c_str());
372  return false;
373  } else if (result != std::cv_status::no_timeout) {
375  LLDB_LOGF(log, "%p Listener::GetEventInternal() unknown error for %s",
376  static_cast<void *>(this), m_name.c_str());
377  return false;
378  }
379  }
380  }
381 
382  return false;
383 }
384 
386  Broadcaster *broadcaster, uint32_t event_type_mask, EventSP &event_sp,
387  const Timeout<std::micro> &timeout) {
388  return GetEventInternal(timeout, broadcaster, nullptr, 0, event_type_mask,
389  event_sp);
390 }
391 
393  EventSP &event_sp,
394  const Timeout<std::micro> &timeout) {
395  return GetEventInternal(timeout, broadcaster, nullptr, 0, 0, event_sp);
396 }
397 
398 bool Listener::GetEvent(EventSP &event_sp, const Timeout<std::micro> &timeout) {
399  return GetEventInternal(timeout, nullptr, nullptr, 0, 0, event_sp);
400 }
401 
402 size_t Listener::HandleBroadcastEvent(EventSP &event_sp) {
403  size_t num_handled = 0;
404  std::lock_guard<std::recursive_mutex> guard(m_broadcasters_mutex);
405  Broadcaster *broadcaster = event_sp->GetBroadcaster();
406  if (!broadcaster)
407  return 0;
408  broadcaster_collection::iterator pos;
409  broadcaster_collection::iterator end = m_broadcasters.end();
410  Broadcaster::BroadcasterImplSP broadcaster_impl_sp(
411  broadcaster->GetBroadcasterImpl());
412  for (pos = m_broadcasters.find(broadcaster_impl_sp);
413  pos != end && pos->first.lock() == broadcaster_impl_sp; ++pos) {
414  BroadcasterInfo info = pos->second;
415  if (event_sp->GetType() & info.event_mask) {
416  if (info.callback != nullptr) {
417  info.callback(event_sp, info.callback_user_data);
418  ++num_handled;
419  }
420  }
421  }
422  return num_handled;
423 }
424 
425 uint32_t
426 Listener::StartListeningForEventSpec(const BroadcasterManagerSP &manager_sp,
427  const BroadcastEventSpec &event_spec) {
428  if (!manager_sp)
429  return 0;
430 
431  // The BroadcasterManager mutex must be locked before m_broadcasters_mutex to
432  // avoid violating the lock hierarchy (manager before broadcasters).
433  std::lock_guard<std::recursive_mutex> manager_guard(
434  manager_sp->m_manager_mutex);
435  std::lock_guard<std::recursive_mutex> guard(m_broadcasters_mutex);
436 
437  uint32_t bits_acquired = manager_sp->RegisterListenerForEvents(
438  this->shared_from_this(), event_spec);
439  if (bits_acquired) {
440  broadcaster_manager_collection::iterator iter,
441  end_iter = m_broadcaster_managers.end();
442  BroadcasterManagerWP manager_wp(manager_sp);
443  BroadcasterManagerWPMatcher matcher(manager_sp);
444  iter = std::find_if<broadcaster_manager_collection::iterator,
445  BroadcasterManagerWPMatcher>(
446  m_broadcaster_managers.begin(), end_iter, matcher);
447  if (iter == end_iter)
448  m_broadcaster_managers.push_back(manager_wp);
449  }
450 
451  return bits_acquired;
452 }
453 
454 bool Listener::StopListeningForEventSpec(const BroadcasterManagerSP &manager_sp,
455  const BroadcastEventSpec &event_spec) {
456  if (!manager_sp)
457  return false;
458 
459  std::lock_guard<std::recursive_mutex> guard(m_broadcasters_mutex);
460  return manager_sp->UnregisterListenerForEvents(this->shared_from_this(),
461  event_spec);
462 }
463 
464 ListenerSP Listener::MakeListener(const char *name) {
465  return ListenerSP(new Listener(name));
466 }
lldb_private::Listener::m_broadcasters_mutex
std::recursive_mutex m_broadcasters_mutex
Definition: Listener.h:132
lldb_private::Listener::FindNextEventInternal
bool FindNextEventInternal(std::unique_lock< std::mutex > &lock, Broadcaster *broadcaster, const ConstString *sources, uint32_t num_sources, uint32_t event_type_mask, lldb::EventSP &event_sp, bool remove)
Definition: Listener.cpp:263
lldb_private::Listener::m_events_mutex
std::mutex m_events_mutex
Definition: Listener.h:134
lldb_private::Listener::HandleBroadcastEvent
size_t HandleBroadcastEvent(lldb::EventSP &event_sp)
Definition: Listener.cpp:402
lldb_private::Event
Definition: Event.h:180
lldb_private::Listener::m_broadcasters
broadcaster_collection m_broadcasters
Definition: Listener.h:131
lldb_private::Listener::StartListeningForEventSpec
uint32_t StartListeningForEventSpec(const lldb::BroadcasterManagerSP &manager_sp, const BroadcastEventSpec &event_spec)
Definition: Listener.cpp:426
LLDB_LOGF
#define LLDB_LOGF(log,...)
Definition: Log.h:249
EventMatcher::m_broadcaster_names
const ConstString * m_broadcaster_names
Definition: Listener.cpp:258
lldb_private::Listener::BroadcasterInfo::callback_user_data
void * callback_user_data
Definition: Listener.h:107
lldb_private::Listener::GetEventInternal
bool GetEventInternal(const Timeout< std::micro > &timeout, Broadcaster *broadcaster, const ConstString *sources, uint32_t num_sources, uint32_t event_type_mask, lldb::EventSP &event_sp)
Definition: Listener.cpp:345
lldb_private::Listener::PeekAtNextEventForBroadcasterWithType
Event * PeekAtNextEventForBroadcasterWithType(Broadcaster *broadcaster, uint32_t event_type_mask)
Definition: Listener.cpp:335
lldb_private::Broadcaster::BroadcasterImplWP
std::weak_ptr< BroadcasterImpl > BroadcasterImplWP
Definition: Broadcaster.h:522
lldb_private::Listener::GetName
const char * GetName()
Definition: Listener.h:59
EventMatcher::operator()
bool operator()(const EventSP &event_sp) const
Definition: Listener.cpp:235
LIBLLDB_LOG_EVENTS
#define LIBLLDB_LOG_EVENTS
Definition: Logging.h:18
Listener.h
Broadcaster.h
lldb_private::Listener::GetEventForBroadcasterWithType
bool GetEventForBroadcasterWithType(Broadcaster *broadcaster, uint32_t event_type_mask, lldb::EventSP &event_sp, const Timeout< std::micro > &timeout)
Definition: Listener.cpp:385
Log.h
EventBroadcasterMatches
Definition: Listener.cpp:214
EventBroadcasterMatches::m_broadcaster
Broadcaster * m_broadcaster
Definition: Listener.cpp:224
lldb_private::Listener::~Listener
~Listener()
Definition: Listener.cpp:49
lldb_private::Listener::BroadcasterInfo
Definition: Listener.h:100
lldb_private::ConstString
Definition: ConstString.h:40
lldb_private::Listener::StartListeningForEvents
uint32_t StartListeningForEvents(Broadcaster *broadcaster, uint32_t event_mask)
Definition: Listener.cpp:84
lldb_private::Listener::AddEvent
void AddEvent(lldb::EventSP &event)
Definition: Listener.cpp:202
lldb_private::GetLogIfAllCategoriesSet
Log * GetLogIfAllCategoriesSet(uint32_t mask)
Definition: Logging.cpp:58
lldb_private::Listener::PeekAtNextEventForBroadcaster
Event * PeekAtNextEventForBroadcaster(Broadcaster *broadcaster)
Definition: Listener.cpp:326
lldb_private::Broadcaster::AddListener
uint32_t AddListener(const lldb::ListenerSP &listener_sp, uint32_t event_mask)
Listen for any events specified by event_mask.
Definition: Broadcaster.h:308
lldb_private::Listener::PeekAtNextEvent
Event * PeekAtNextEvent()
Definition: Listener.cpp:318
lldb_private::Broadcaster::RemoveListener
bool RemoveListener(const lldb::ListenerSP &listener_sp, uint32_t event_mask=UINT32_MAX)
Removes a Listener from this broadcasters list and frees the event bits specified by event_mask that ...
Definition: Broadcaster.h:365
Event.h
lldb_private::Listener::Clear
void Clear()
Definition: Listener.cpp:58
EventMatcher::m_broadcaster
Broadcaster * m_broadcaster
Definition: Listener.cpp:257
lldb_private::Broadcaster::BroadcasterImplSP
std::shared_ptr< BroadcasterImpl > BroadcasterImplSP
Definition: Broadcaster.h:521
lldb_private::Listener::StopListeningForEventSpec
bool StopListeningForEventSpec(const lldb::BroadcasterManagerSP &manager_sp, const BroadcastEventSpec &event_spec)
Definition: Listener.cpp:454
lldb_private::Listener::BroadcasterInfo::event_mask
uint32_t event_mask
Definition: Listener.h:105
EventMatcher::EventMatcher
EventMatcher(Broadcaster *broadcaster, const ConstString *broadcaster_names, uint32_t num_broadcaster_names, uint32_t event_type_mask)
Definition: Listener.cpp:229
lldb_private::Listener::Listener
Listener(const char *name)
Definition: Listener.cpp:40
LIBLLDB_LOG_OBJECT
#define LIBLLDB_LOG_OBJECT
Definition: Logging.h:25
lldb_private::Timeout< std::micro >
lldb_private::Listener::GetEvent
bool GetEvent(lldb::EventSP &event_sp, const Timeout< std::micro > &timeout)
Definition: Listener.cpp:398
uint32_t
lldb_private::Listener::BroadcasterManagerWillDestruct
void BroadcasterManagerWillDestruct(lldb::BroadcasterManagerSP manager_sp)
Definition: Listener.cpp:188
lldb_private::Listener::m_events
event_collection m_events
Definition: Listener.h:133
EventBroadcasterMatches::operator()
bool operator()(const EventSP &event_sp) const
Definition: Listener.cpp:219
EventMatcher
Definition: Listener.cpp:227
lldb_private::BroadcastEventSpec
lldb::BroadcastEventSpec
Definition: Broadcaster.h:40
lldb_private::Listener::m_name
std::string m_name
Definition: Listener.h:130
lldb_private::Listener::StopListeningForEvents
bool StopListeningForEvents(Broadcaster *broadcaster, uint32_t event_mask)
Definition: Listener.cpp:148
lldb_private::Listener::BroadcasterWillDestruct
void BroadcasterWillDestruct(Broadcaster *)
Definition: Listener.cpp:166
LLDB_LOG
#define LLDB_LOG(log,...)
The LLDB_LOG* macros defined below are the way to emit log messages.
Definition: Log.h:242
lldb_private
A class that represents a running process on the host machine.
Definition: SBCommandInterpreterRunOptions.h:16
EventBroadcasterMatches::EventBroadcasterMatches
EventBroadcasterMatches(Broadcaster *broadcaster)
Definition: Listener.cpp:216
lldb_private::Listener::BroadcasterInfo::callback
HandleBroadcastCallback callback
Definition: Listener.h:106
ConstString.h
Logging.h
lldb_private::Listener::m_broadcaster_managers
broadcaster_manager_collection m_broadcaster_managers
Definition: Listener.h:136
lldb_private::Log
Definition: Log.h:49
lldb_private::Listener::MakeListener
static lldb::ListenerSP MakeListener(const char *name)
Definition: Listener.cpp:464
EventMatcher::m_num_broadcaster_names
const uint32_t m_num_broadcaster_names
Definition: Listener.cpp:259
lldb_private::Broadcaster
Definition: Broadcaster.h:242
lldb_private::Listener::GetEventForBroadcaster
bool GetEventForBroadcaster(Broadcaster *broadcaster, lldb::EventSP &event_sp, const Timeout< std::micro > &timeout)
Definition: Listener.cpp:392
lldb
Definition: SBAddress.h:15
lldb_private::Listener::m_events_condition
std::condition_variable m_events_condition
Definition: Listener.h:135
EventMatcher::m_event_type_mask
const uint32_t m_event_type_mask
Definition: Listener.cpp:260
lldb_private::Broadcaster::GetBroadcasterImpl
BroadcasterImplSP GetBroadcasterImpl()
Definition: Broadcaster.h:524