Queue.h
Go to the documentation of this file.
1 /*
2  * Copyright (C) 1996-2017 The Squid Software Foundation and contributors
3  *
4  * Squid software is distributed under GPLv2+ license and includes
5  * contributions from numerous individuals and organizations.
6  * Please see the COPYING and CONTRIBUTORS files for details.
7  */
8 
9 #ifndef SQUID_IPC_QUEUE_H
10 #define SQUID_IPC_QUEUE_H
11 
12 #include "base/InstanceId.h"
13 #include "Debug.h"
14 #include "ipc/mem/FlexibleArray.h"
15 #include "ipc/mem/Pointer.h"
16 #include "util.h"
17 
18 #include <atomic>
19 
20 class String;
21 
22 namespace Ipc
23 {
24 
28 {
29 public:
30  QueueReader(); // the initial state is "blocked without a signal"
31 
33  bool blocked() const { return popBlocked.load(); }
34 
36  void block() { popBlocked.store(true); }
37 
39  void unblock() { popBlocked.store(false); }
40 
43  bool raiseSignal() { return blocked() && !popSignal.exchange(true); }
44 
46  void clearSignal() { unblock(); popSignal.store(false); }
47 
48 private:
49  std::atomic<bool> popBlocked;
50  std::atomic<bool> popSignal;
51 
52 public:
53  typedef std::atomic<int> Rate;
55 
56  // we need a signed atomic type because balance may get negative
57  typedef std::atomic<int> AtomicSignedMsec;
61 
64 };
65 
68 {
69 public:
70  QueueReaders(const int aCapacity);
71  size_t sharedMemorySize() const;
72  static size_t SharedMemorySize(const int capacity);
73 
74  const int theCapacity;
76 };
77 
89 {
90 public:
91  // pop() and push() exceptions; TODO: use TextException instead
92  class Full {};
93  class ItemTooLarge {};
94 
95  OneToOneUniQueue(const unsigned int aMaxItemSize, const int aCapacity);
96 
97  unsigned int maxItemSize() const { return theMaxItemSize; }
98  int size() const { return theSize; }
99  int capacity() const { return theCapacity; }
101 
102  bool empty() const { return !theSize; }
103  bool full() const { return theSize == theCapacity; }
104 
105  static int Bytes2Items(const unsigned int maxItemSize, int size);
106  static int Items2Bytes(const unsigned int maxItemSize, const int size);
107 
109  template<class Value> bool pop(Value &value, QueueReader *const reader = NULL);
110 
112  template<class Value> bool push(const Value &value, QueueReader *const reader = NULL);
113 
115  template<class Value> bool peek(Value &value) const;
116 
117 private:
118 
119  unsigned int theIn;
120  unsigned int theOut;
121 
122  std::atomic<uint32_t> theSize;
123  const unsigned int theMaxItemSize;
124  const uint32_t theCapacity;
125 
126  char theBuffer[];
127 };
128 
131 {
132 public:
133  OneToOneUniQueues(const int aCapacity, const unsigned int maxItemSize, const int queueCapacity);
134 
135  size_t sharedMemorySize() const;
136  static size_t SharedMemorySize(const int capacity, const unsigned int maxItemSize, const int queueCapacity);
137 
138  const OneToOneUniQueue &operator [](const int index) const;
139  inline OneToOneUniQueue &operator [](const int index);
140 
141 private:
142  inline const OneToOneUniQueue &front() const;
143 
144 public:
145  const int theCapacity;
146 };
147 
153 {
154 public:
155  BaseMultiQueue(const int aLocalProcessId);
156  virtual ~BaseMultiQueue() {}
157 
159  void clearReaderSignal(const int remoteProcessId);
160 
162  template <class Value> bool pop(int &remoteProcessId, Value &value);
163 
165  template <class Value> bool push(const int remoteProcessId, const Value &value);
166 
168  template<class Value> bool peek(int &remoteProcessId, Value &value) const;
169 
172 
174  const QueueReader::Balance &balance(const int remoteProcessId) const;
175 
178 
180  const QueueReader::Rate &rateLimit(const int remoteProcessId) const;
181 
183  int inSize(const int remoteProcessId) const { return inQueue(remoteProcessId).size(); }
184 
186  int outSize(const int remoteProcessId) const { return outQueue(remoteProcessId).size(); }
187 
188 protected:
190  virtual const OneToOneUniQueue &inQueue(const int remoteProcessId) const = 0;
191  OneToOneUniQueue &inQueue(const int remoteProcessId);
192 
194  virtual const OneToOneUniQueue &outQueue(const int remoteProcessId) const = 0;
195  OneToOneUniQueue &outQueue(const int remoteProcessId);
196 
197  virtual const QueueReader &localReader() const = 0;
199 
200  virtual const QueueReader &remoteReader(const int remoteProcessId) const = 0;
201  QueueReader &remoteReader(const int remoteProcessId);
202 
203  virtual int remotesCount() const = 0;
204  virtual int remotesIdOffset() const = 0;
205 
206 protected:
207  const int theLocalProcessId;
208 
209 private:
211 };
212 
222 {
223 public:
226 
227 private:
229  struct Metadata {
230  Metadata(const int aGroupASize, const int aGroupAIdOffset, const int aGroupBSize, const int aGroupBIdOffset);
231  size_t sharedMemorySize() const { return sizeof(*this); }
232  static size_t SharedMemorySize(const int, const int, const int, const int) { return sizeof(Metadata); }
233 
234  const int theGroupASize;
235  const int theGroupAIdOffset;
236  const int theGroupBSize;
237  const int theGroupBIdOffset;
238  };
239 
240 public:
241  class Owner
242  {
243  public:
244  Owner(const String &id, const int groupASize, const int groupAIdOffset, const int groupBSize, const int groupBIdOffset, const unsigned int maxItemSize, const int capacity);
245  ~Owner();
246 
247  private:
251  };
252 
253  static Owner *Init(const String &id, const int groupASize, const int groupAIdOffset, const int groupBSize, const int groupBIdOffset, const unsigned int maxItemSize, const int capacity);
254 
255  enum Group { groupA = 0, groupB = 1 };
256  FewToFewBiQueue(const String &id, const Group aLocalGroup, const int aLocalProcessId);
257 
259  static int MaxItemsCount(const int groupASize, const int groupBSize, const int capacity);
260 
263  template<class Value> bool findOldest(const int remoteProcessId, Value &value) const;
264 
265 protected:
266  virtual const OneToOneUniQueue &inQueue(const int remoteProcessId) const;
267  virtual const OneToOneUniQueue &outQueue(const int remoteProcessId) const;
268  virtual const QueueReader &localReader() const;
269  virtual const QueueReader &remoteReader(const int processId) const;
270  virtual int remotesCount() const;
271  virtual int remotesIdOffset() const;
272 
273 private:
274  bool validProcessId(const Group group, const int processId) const;
275  int oneToOneQueueIndex(const Group fromGroup, const int fromProcessId, const Group toGroup, const int toProcessId) const;
276  const OneToOneUniQueue &oneToOneQueue(const Group fromGroup, const int fromProcessId, const Group toGroup, const int toProcessId) const;
277  int readerIndex(const Group group, const int processId) const;
278  Group localGroup() const { return theLocalGroup; }
279  Group remoteGroup() const { return theLocalGroup == groupA ? groupB : groupA; }
280 
281 private:
285 
287 };
288 
296 {
297 public:
300 
301 private:
303  struct Metadata {
304  Metadata(const int aProcessCount, const int aProcessIdOffset);
305  size_t sharedMemorySize() const { return sizeof(*this); }
306  static size_t SharedMemorySize(const int, const int) { return sizeof(Metadata); }
307 
308  const int theProcessCount;
310  };
311 
312 public:
313  class Owner
314  {
315  public:
316  Owner(const String &id, const int processCount, const int processIdOffset, const unsigned int maxItemSize, const int capacity);
317  ~Owner();
318 
319  private:
323  };
324 
325  static Owner *Init(const String &id, const int processCount, const int processIdOffset, const unsigned int maxItemSize, const int capacity);
326 
327  MultiQueue(const String &id, const int localProcessId);
328 
329 protected:
330  virtual const OneToOneUniQueue &inQueue(const int remoteProcessId) const;
331  virtual const OneToOneUniQueue &outQueue(const int remoteProcessId) const;
332  virtual const QueueReader &localReader() const;
333  virtual const QueueReader &remoteReader(const int remoteProcessId) const;
334  virtual int remotesCount() const;
335  virtual int remotesIdOffset() const;
336 
337 private:
338  bool validProcessId(const int processId) const;
339  const OneToOneUniQueue &oneToOneQueue(const int fromProcessId, const int toProcessId) const;
340  const QueueReader &reader(const int processId) const;
341 
342 private:
346 };
347 
348 // OneToOneUniQueue
349 
350 template <class Value>
351 bool
352 OneToOneUniQueue::pop(Value &value, QueueReader *const reader)
353 {
354  if (sizeof(value) > theMaxItemSize)
355  throw ItemTooLarge();
356 
357  // A writer might push between the empty test and block() below, so we do
358  // not return false right after calling block(), but test again.
359  if (empty()) {
360  if (!reader)
361  return false;
362 
363  reader->block();
364  // A writer might push between the empty test and block() below,
365  // so we must test again as such a writer will not signal us.
366  if (empty())
367  return false;
368  }
369 
370  if (reader)
371  reader->unblock();
372 
373  const unsigned int pos = (theOut++ % theCapacity) * theMaxItemSize;
374  memcpy(&value, theBuffer + pos, sizeof(value));
375  --theSize;
376 
377  return true;
378 }
379 
380 template <class Value>
381 bool
382 OneToOneUniQueue::peek(Value &value) const
383 {
384  if (sizeof(value) > theMaxItemSize)
385  throw ItemTooLarge();
386 
387  if (empty())
388  return false;
389 
390  // the reader may pop() before we copy; making this method imprecise
391  const unsigned int pos = (theOut % theCapacity) * theMaxItemSize;
392  memcpy(&value, theBuffer + pos, sizeof(value));
393  return true;
394 }
395 
396 template <class Value>
397 bool
398 OneToOneUniQueue::push(const Value &value, QueueReader *const reader)
399 {
400  if (sizeof(value) > theMaxItemSize)
401  throw ItemTooLarge();
402 
403  if (full())
404  throw Full();
405 
406  const unsigned int pos = theIn++ % theCapacity * theMaxItemSize;
407  memcpy(theBuffer + pos, &value, sizeof(value));
408  const bool wasEmpty = !theSize++;
409 
410  return wasEmpty && (!reader || reader->raiseSignal());
411 }
412 
413 // OneToOneUniQueues
414 
415 inline OneToOneUniQueue &
417 {
418  return const_cast<OneToOneUniQueue &>((*const_cast<const OneToOneUniQueues *>(this))[index]);
419 }
420 
421 inline const OneToOneUniQueue &
423 {
424  const char *const queue =
425  reinterpret_cast<const char *>(this) + sizeof(*this);
426  return *reinterpret_cast<const OneToOneUniQueue *>(queue);
427 }
428 
429 // BaseMultiQueue
430 
431 template <class Value>
432 bool
433 BaseMultiQueue::pop(int &remoteProcessId, Value &value)
434 {
435  // iterate all remote processes, starting after the one we visited last
436  for (int i = 0; i < remotesCount(); ++i) {
440  if (queue.pop(value, &localReader())) {
441  remoteProcessId = theLastPopProcessId;
442  debugs(54, 7, HERE << "popped from " << remoteProcessId << " to " << theLocalProcessId << " at " << queue.size());
443  return true;
444  }
445  }
446  return false; // no process had anything to pop
447 }
448 
449 template <class Value>
450 bool
451 BaseMultiQueue::push(const int remoteProcessId, const Value &value)
452 {
453  OneToOneUniQueue &remoteQueue = outQueue(remoteProcessId);
454  QueueReader &reader = remoteReader(remoteProcessId);
455  debugs(54, 7, HERE << "pushing from " << theLocalProcessId << " to " << remoteProcessId << " at " << remoteQueue.size());
456  return remoteQueue.push(value, &reader);
457 }
458 
459 template <class Value>
460 bool
461 BaseMultiQueue::peek(int &remoteProcessId, Value &value) const
462 {
463  // mimic FewToFewBiQueue::pop() but quit just before popping
464  int popProcessId = theLastPopProcessId; // preserve for future pop()
465  for (int i = 0; i < remotesCount(); ++i) {
466  if (++popProcessId >= remotesIdOffset() + remotesCount())
467  popProcessId = remotesIdOffset();
468  const OneToOneUniQueue &queue = inQueue(popProcessId);
469  if (queue.peek(value)) {
470  remoteProcessId = popProcessId;
471  return true;
472  }
473  }
474  return false; // most likely, no process had anything to pop
475 }
476 
477 // FewToFewBiQueue
478 
479 template <class Value>
480 bool
481 FewToFewBiQueue::findOldest(const int remoteProcessId, Value &value) const
482 {
483  // we may be called before remote process configured its queue end
484  if (!validProcessId(remoteGroup(), remoteProcessId))
485  return false;
486 
487  // we need the oldest value, so start with the incoming, them-to-us queue:
488  const OneToOneUniQueue &in = inQueue(remoteProcessId);
489  debugs(54, 2, HERE << "peeking from " << remoteProcessId << " to " <<
490  theLocalProcessId << " at " << in.size());
491  if (in.peek(value))
492  return true;
493 
494  // if the incoming queue is empty, check the outgoing, us-to-them queue:
495  const OneToOneUniQueue &out = outQueue(remoteProcessId);
496  debugs(54, 2, HERE << "peeking from " << theLocalProcessId << " to " <<
497  remoteProcessId << " at " << out.size());
498  return out.peek(value);
499 }
500 
501 } // namespace Ipc
502 
503 #endif // SQUID_IPC_QUEUE_H
504 
int sharedMemorySize() const
Definition: Queue.h:100
void clearReaderSignal(const int remoteProcessId)
clears the reader notification received by the local process from the remote process ...
Definition: Queue.cc:140
Group remoteGroup() const
Definition: Queue.h:279
QueueReader::Balance & localBalance()
returns local reader's balance
Definition: Queue.h:171
virtual const OneToOneUniQueue & inQueue(const int remoteProcessId) const
incoming queue from a given remote process
Definition: Queue.cc:386
const OneToOneUniQueue & oneToOneQueue(const Group fromGroup, const int fromProcessId, const Group toGroup, const int toProcessId) const
Definition: Queue.cc:263
OneToOneUniQueues(const int aCapacity, const unsigned int maxItemSize, const int queueCapacity)
Definition: Queue.cc:100
Mem::Owner< OneToOneUniQueues > *const queuesOwner
Definition: Queue.h:249
const int theLocalProcessId
process ID of this queue
Definition: Queue.h:207
bool raiseSignal()
Definition: Queue.h:43
OneToOneUniQueue::ItemTooLarge ItemTooLarge
Definition: Queue.h:225
std::atomic< int > AtomicSignedMsec
Definition: Queue.h:57
virtual int remotesCount() const =0
const int theProcessIdOffset
Definition: Queue.h:309
int theLastPopProcessId
the ID of the last process we tried to pop() from
Definition: Queue.h:210
const unsigned int theMaxItemSize
maximum item size
Definition: Queue.h:123
OneToOneUniQueue::ItemTooLarge ItemTooLarge
Definition: Queue.h:299
Mem::Owner< QueueReaders > *const readersOwner
Definition: Queue.h:322
std::atomic< int > Rate
pop()s per second
Definition: Queue.h:53
size_t sharedMemorySize() const
Definition: Queue.h:231
const OneToOneUniQueue & front() const
Definition: Queue.h:422
static size_t SharedMemorySize(const int capacity)
Definition: Queue.cc:68
int oneToOneQueueIndex(const Group fromGroup, const int fromProcessId, const Group toGroup, const int toProcessId) const
Definition: Queue.cc:241
bool findOldest(const int remoteProcessId, Value &value) const
Definition: Queue.h:481
virtual int remotesIdOffset() const
Definition: Queue.cc:416
QueueReaders(const int aCapacity)
Definition: Queue.cc:55
virtual const QueueReader & remoteReader(const int remoteProcessId) const =0
Group localGroup() const
Definition: Queue.h:278
Metadata(const int aProcessCount, const int aProcessIdOffset)
Definition: Queue.cc:421
int i
Definition: membanger.c:49
virtual const QueueReader & remoteReader(const int processId) const
Definition: Queue.cc:298
virtual const OneToOneUniQueue & outQueue(const int remoteProcessId) const =0
outgoing queue to a given remote process
Owner(const String &id, const int processCount, const int processIdOffset, const unsigned int maxItemSize, const int capacity)
Definition: Queue.cc:427
bool pop(int &remoteProcessId, Value &value)
picks a process and calls OneToOneUniQueue::pop() using its queue
Definition: Queue.h:433
const OneToOneUniQueue & oneToOneQueue(const int fromProcessId, const int toProcessId) const
Definition: Queue.cc:367
virtual const QueueReader & remoteReader(const int remoteProcessId) const
Definition: Queue.cc:404
static int Items2Bytes(const unsigned int maxItemSize, const int size)
Definition: Queue.cc:92
Shared metadata for MultiQueue.
Definition: Queue.h:303
bool peek(int &remoteProcessId, Value &value) const
peeks at the item likely to be pop()ed next
Definition: Queue.h:461
bool full() const
Definition: Queue.h:103
bool validProcessId(const int processId) const
Definition: Queue.cc:360
size_t sharedMemorySize() const
Definition: Queue.cc:62
unsigned int theOut
output index, used only in pop()
Definition: Queue.h:120
Balance balance
how far ahead the reader is compared to a perfect read/sec event rate
Definition: Queue.h:60
Rate rateLimit
pop()s per second limit if positive
Definition: Queue.h:54
std::atomic< bool > popBlocked
whether the reader is blocked on pop()
Definition: Queue.h:49
Metadata(const int aGroupASize, const int aGroupAIdOffset, const int aGroupBSize, const int aGroupBIdOffset)
Definition: Queue.cc:317
void unblock()
removes the block() effects
Definition: Queue.h:39
const int theCapacity
Definition: Queue.h:74
virtual const OneToOneUniQueue & inQueue(const int remoteProcessId) const
incoming queue from a given remote process
Definition: Queue.cc:269
size_t sharedMemorySize() const
Definition: Queue.cc:108
virtual ~BaseMultiQueue()
Definition: Queue.h:156
unsigned int maxItemSize() const
Definition: Queue.h:97
FewToFewBiQueue(const String &id, const Group aLocalGroup, const int aLocalProcessId)
Definition: Queue.cc:207
void clearSignal()
marks sent reader notification as received (also removes pop blocking)
Definition: Queue.h:46
void block()
marks the reader as blocked, waiting for a notification signal
Definition: Queue.h:36
#define debugs(SECTION, LEVEL, CONTENT)
Definition: Debug.h:123
virtual const OneToOneUniQueue & outQueue(const int remoteProcessId) const
outgoing queue to a given remote process
Definition: Queue.cc:392
static size_t SharedMemorySize(const int, const int, const int, const int)
Definition: Queue.h:232
const Mem::Pointer< OneToOneUniQueues > queues
unidirection one-to-one queues
Definition: Queue.h:344
virtual const QueueReader & localReader() const
Definition: Queue.cc:292
static Owner * Init(const String &id, const int groupASize, const int groupAIdOffset, const int groupBSize, const int groupBIdOffset, const unsigned int maxItemSize, const int capacity)
Definition: Queue.cc:202
virtual int remotesIdOffset() const =0
virtual const OneToOneUniQueue & inQueue(const int remoteProcessId) const =0
incoming queue from a given remote process
const QueueReader::Balance & balance(const int remoteProcessId) const
returns reader's balance for a given remote process
Definition: Queue.cc:154
unsigned int theIn
input index, used only in push()
Definition: Queue.h:119
shared array of OneToOneUniQueues
Definition: Queue.h:130
const Mem::Pointer< QueueReaders > readers
readers array
Definition: Queue.h:284
shared array of QueueReaders
Definition: Queue.h:67
static size_t SharedMemorySize(const int, const int)
Definition: Queue.h:306
const uint32_t theCapacity
maximum number of items, i.e. theBuffer size
Definition: Queue.h:124
const Group theLocalGroup
group of this queue
Definition: Queue.h:286
OneToOneUniQueue(const unsigned int aMaxItemSize, const int aCapacity)
Definition: Queue.cc:75
virtual const OneToOneUniQueue & outQueue(const int remoteProcessId) const
outgoing queue to a given remote process
Definition: Queue.cc:276
virtual int remotesIdOffset() const
Definition: Queue.cc:311
bool empty() const
Definition: Queue.h:102
int readerIndex(const Group group, const int processId) const
Definition: Queue.cc:283
Ipc::Mem::FlexibleArray< QueueReader > theReaders
number of readers
Definition: Queue.h:75
std::ostream & HERE(std::ostream &s)
Definition: Debug.h:147
int inSize(const int remoteProcessId) const
number of items in incoming queue from a given remote process
Definition: Queue.h:183
const Mem::Pointer< Metadata > metadata
shared metadata
Definition: Queue.h:282
Shared metadata for FewToFewBiQueue.
Definition: Queue.h:229
bool blocked() const
whether the reader is waiting for a notification signal
Definition: Queue.h:33
const Mem::Pointer< QueueReaders > readers
readers array
Definition: Queue.h:345
QueueReader::Rate & localRateLimit()
returns local reader's rate limit
Definition: Queue.h:177
static int Bytes2Items(const unsigned int maxItemSize, int size)
Definition: Queue.cc:84
static Owner * Init(const String &id, const int processCount, const int processIdOffset, const unsigned int maxItemSize, const int capacity)
Definition: Queue.cc:342
const QueueReader::Rate & rateLimit(const int remoteProcessId) const
returns reader's rate limit for a given remote process
Definition: Queue.cc:161
MultiQueue(const String &id, const int localProcessId)
Definition: Queue.cc:347
const int theProcessCount
Definition: Queue.h:308
const OneToOneUniQueue & operator[](const int index) const
Definition: Queue.cc:122
Mem::Owner< Metadata > *const metadataOwner
Definition: Queue.h:320
std::atomic< uint32_t > theSize
number of items in the queue
Definition: Queue.h:122
int outSize(const int remoteProcessId) const
number of items in outgoing queue to a given remote process
Definition: Queue.h:186
bool push(const Value &value, QueueReader *const reader=NULL)
returns true iff the caller must notify the reader of the pushed item
Definition: Queue.h:398
int size() const
Definition: Queue.h:98
static int MaxItemsCount(const int groupASize, const int groupBSize, const int capacity)
maximum number of items in the queue
Definition: Queue.cc:221
BaseMultiQueue(const int aLocalProcessId)
Definition: Queue.cc:133
bool peek(Value &value) const
returns true iff the value was set; the value may be stale!
Definition: Queue.h:382
static size_t SharedMemorySize(const int capacity, const unsigned int maxItemSize, const int queueCapacity)
Definition: Queue.cc:114
OneToOneUniQueue::Full Full
Definition: Queue.h:224
Mem::Owner< QueueReaders > *const readersOwner
Definition: Queue.h:250
virtual const QueueReader & localReader() const
Definition: Queue.cc:398
bool push(const int remoteProcessId, const Value &value)
calls OneToOneUniQueue::push() using the given process queue
Definition: Queue.h:451
virtual int remotesCount() const
Definition: Queue.cc:410
std::atomic< bool > popSignal
whether writer has sent and reader has not received notification
Definition: Queue.h:50
const Mem::Pointer< Metadata > metadata
shared metadata
Definition: Queue.h:343
const QueueReader & reader(const int processId) const
Definition: Queue.cc:378
bool validProcessId(const Group group, const int processId) const
Definition: Queue.cc:227
size_t sharedMemorySize() const
Definition: Queue.h:305
virtual int remotesCount() const
Definition: Queue.cc:304
Mem::Owner< OneToOneUniQueues > *const queuesOwner
Definition: Queue.h:321
const int theCapacity
Definition: Queue.h:145
AtomicSignedMsec Balance
Definition: Queue.h:58
virtual const QueueReader & localReader() const =0
bool pop(Value &value, QueueReader *const reader=NULL)
returns true iff the value was set; [un]blocks the reader as needed
Definition: Queue.h:352
Owner(const String &id, const int groupASize, const int groupAIdOffset, const int groupBSize, const int groupBIdOffset, const unsigned int maxItemSize, const int capacity)
Definition: Queue.cc:325
const InstanceId< QueueReader > id
unique ID for debugging which reader is used (works across processes)
Definition: Queue.h:63
Mem::Owner< Metadata > *const metadataOwner
Definition: Queue.h:248
#define NULL
Definition: types.h:166
OneToOneUniQueue::Full Full
Definition: Queue.h:298
const Mem::Pointer< OneToOneUniQueues > queues
unidirection one-to-one queues
Definition: Queue.h:283
int capacity() const
Definition: Queue.h:99

 

Introduction

Documentation

Support

Miscellaneous

Web Site Translations

Mirrors