Coverage Report

Created: 2026-10-15 06:31

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/FreeRDP/winpr/libwinpr/utils/collections/StreamPool.c
Line
Count
Source
1
/**
2
 * WinPR: Windows Portable Runtime
3
 * Object Pool
4
 *
5
 * Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
6
 *
7
 * Licensed under the Apache License, Version 2.0 (the "License");
8
 * you may not use this file except in compliance with the License.
9
 * You may obtain a copy of the License at
10
 *
11
 *     http://www.apache.org/licenses/LICENSE-2.0
12
 *
13
 * Unless required by applicable law or agreed to in writing, software
14
 * distributed under the License is distributed on an "AS IS" BASIS,
15
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
 * See the License for the specific language governing permissions and
17
 * limitations under the License.
18
 */
19
20
#include <winpr/config.h>
21
22
#include <winpr/crt.h>
23
#include <winpr/wlog.h>
24
25
#include <winpr/collections.h>
26
27
#include "../stream.h"
28
#include "../log.h"
29
22.4k
#define XTAG WINPR_TAG("utils.streampool")
30
31
#if !defined(STREAMPOOL_SIZE_LIMIT)
32
#error "CMake must define STREAMPOOL_SIZE_LIMIT=<unsigned>"
33
#endif
34
static const size_t POOL_COMMON_LIMIT = STREAMPOOL_SIZE_LIMIT;
35
36
struct s_StreamPoolEntry
37
{
38
#if defined(WITH_DEBUG_STREAMPOOL)
39
  char** msg;
40
  size_t lines;
41
#endif
42
  wStream* s;
43
};
44
45
struct s_wStreamPool
46
{
47
  size_t aSize;
48
  size_t aCapacity;
49
  struct s_StreamPoolEntry* aArray;
50
51
  size_t uSize;
52
  size_t uCapacity;
53
  struct s_StreamPoolEntry* uArray;
54
55
  CRITICAL_SECTION lock;
56
  BOOL synchronized;
57
  size_t defaultSize;
58
  wLog* log;
59
};
60
61
static void discard_entry(struct s_StreamPoolEntry* entry, BOOL discardStream)
62
77.6k
{
63
77.6k
  if (!entry)
64
0
    return;
65
66
77.6k
#if defined(WITH_DEBUG_STREAMPOOL)
67
77.6k
  free((void*)entry->msg);
68
77.6k
#endif
69
70
77.6k
  if (discardStream && entry->s)
71
23.1k
    Stream_Free(entry->s, entry->s->isAllocatedStream);
72
73
77.6k
  const struct s_StreamPoolEntry empty = WINPR_C_ARRAY_INIT;
74
77.6k
  *entry = empty;
75
77.6k
}
76
77
static struct s_StreamPoolEntry add_entry(wStream* s)
78
77.6k
{
79
77.6k
  struct s_StreamPoolEntry entry = WINPR_C_ARRAY_INIT;
80
81
77.6k
#if defined(WITH_DEBUG_STREAMPOOL)
82
77.6k
  void* stack = winpr_backtrace(20);
83
77.6k
  if (stack)
84
77.6k
    entry.msg = winpr_backtrace_symbols(stack, &entry.lines);
85
77.6k
  winpr_backtrace_free(stack);
86
77.6k
#endif
87
88
77.6k
  entry.s = s;
89
77.6k
  return entry;
90
77.6k
}
91
92
/**
93
 * Lock the stream pool
94
 */
95
96
static inline void StreamPool_Lock(wStreamPool* pool)
97
121k
{
98
121k
  WINPR_ASSERT(pool);
99
121k
  if (pool->synchronized)
100
121k
    EnterCriticalSection(&pool->lock);
101
121k
}
102
103
/**
104
 * Unlock the stream pool
105
 */
106
107
static inline void StreamPool_Unlock(wStreamPool* pool)
108
121k
{
109
121k
  WINPR_ASSERT(pool);
110
121k
  if (pool->synchronized)
111
121k
    LeaveCriticalSection(&pool->lock);
112
121k
}
113
114
static BOOL StreamPool_ShrinkToCommonLimit(wStream* s)
115
38.8k
{
116
38.8k
  if (Stream_Capacity(s) <= POOL_COMMON_LIMIT)
117
38.8k
    return TRUE;
118
119
0
  return Stream_ResizeToCapacity(s, POOL_COMMON_LIMIT);
120
38.8k
}
121
122
static BOOL StreamPool_EnsureCapacity(wStreamPool* pool, size_t count, BOOL usedOrAvailable)
123
122k
{
124
122k
  WINPR_ASSERT(pool);
125
126
122k
  size_t* cap = (usedOrAvailable) ? &pool->uCapacity : &pool->aCapacity;
127
122k
  size_t* size = (usedOrAvailable) ? &pool->uSize : &pool->aSize;
128
122k
  struct s_StreamPoolEntry** array = (usedOrAvailable) ? &pool->uArray : &pool->aArray;
129
130
122k
  size_t new_cap = 0;
131
122k
  if (*cap == 0)
132
44.8k
    new_cap = *size + count;
133
77.6k
  else if (*size + count > *cap)
134
13
    new_cap = (*size + count + 2) / 2 * 3;
135
77.6k
  else if ((*size + count) < *cap / 3)
136
47.0k
    new_cap = *cap / 2;
137
138
122k
  if (new_cap > 0)
139
91.8k
  {
140
91.8k
    struct s_StreamPoolEntry* new_arr = nullptr;
141
142
91.8k
    if (*cap < *size + count)
143
44.8k
      *cap += count;
144
145
91.8k
    new_arr =
146
91.8k
        (struct s_StreamPoolEntry*)realloc(*array, sizeof(struct s_StreamPoolEntry) * new_cap);
147
91.8k
    if (!new_arr)
148
0
      return FALSE;
149
91.8k
    *cap = new_cap;
150
91.8k
    *array = new_arr;
151
91.8k
  }
152
122k
  return TRUE;
153
122k
}
154
155
/**
156
 * Methods
157
 */
158
159
static void StreamPool_ShiftUsed(wStreamPool* pool, size_t index)
160
38.8k
{
161
38.8k
  WINPR_ASSERT(pool);
162
163
38.8k
  const size_t pcount = 1;
164
38.8k
  const size_t off = index + pcount;
165
38.8k
  if (pool->uSize >= off)
166
38.8k
  {
167
77.6k
    for (size_t x = 0; x < pcount; x++)
168
38.8k
    {
169
38.8k
      struct s_StreamPoolEntry* cur = &pool->uArray[index + x];
170
38.8k
      discard_entry(cur, FALSE);
171
38.8k
    }
172
38.8k
    MoveMemory(&pool->uArray[index], &pool->uArray[index + pcount],
173
38.8k
               (pool->uSize - index - pcount) * sizeof(struct s_StreamPoolEntry));
174
38.8k
    pool->uSize -= pcount;
175
38.8k
  }
176
38.8k
}
177
178
/**
179
 * Adds a used stream to the pool.
180
 */
181
182
static void StreamPool_AddUsed(wStreamPool* pool, wStream* s)
183
38.8k
{
184
38.8k
  StreamPool_EnsureCapacity(pool, 1, TRUE);
185
38.8k
  pool->uArray[pool->uSize] = add_entry(s);
186
38.8k
  pool->uSize++;
187
38.8k
}
188
189
/**
190
 * Removes a used stream from the pool.
191
 */
192
193
static void StreamPool_RemoveUsed(wStreamPool* pool, wStream* s)
194
38.8k
{
195
38.8k
  WINPR_ASSERT(pool);
196
40.2k
  for (size_t index = 0; index < pool->uSize; index++)
197
40.2k
  {
198
40.2k
    struct s_StreamPoolEntry* cur = &pool->uArray[index];
199
40.2k
    if (cur->s == s)
200
38.8k
    {
201
38.8k
      StreamPool_ShiftUsed(pool, index);
202
38.8k
      break;
203
38.8k
    }
204
40.2k
  }
205
38.8k
}
206
207
static void StreamPool_ShiftAvailable(wStreamPool* pool, size_t index)
208
15.6k
{
209
15.6k
  WINPR_ASSERT(pool);
210
211
15.6k
  const size_t pcount = 1;
212
15.6k
  const size_t off = index + pcount;
213
15.6k
  if (pool->aSize >= off)
214
15.6k
  {
215
31.3k
    for (size_t x = 0; x < pcount; x++)
216
15.6k
    {
217
15.6k
      struct s_StreamPoolEntry* cur = &pool->aArray[index + x];
218
15.6k
      discard_entry(cur, FALSE);
219
15.6k
    }
220
221
15.6k
    MoveMemory(&pool->aArray[index], &pool->aArray[index + pcount],
222
15.6k
               (pool->aSize - index - pcount) * sizeof(struct s_StreamPoolEntry));
223
15.6k
    pool->aSize -= pcount;
224
15.6k
  }
225
15.6k
}
226
227
/**
228
 * Gets a stream from the pool.
229
 */
230
231
wStream* StreamPool_Take(wStreamPool* pool, size_t size)
232
38.8k
{
233
38.8k
  BOOL found = FALSE;
234
38.8k
  size_t foundIndex = 0;
235
38.8k
  wStream* s = nullptr;
236
237
38.8k
  StreamPool_Lock(pool);
238
239
38.8k
  if (size == 0)
240
31.5k
    size = pool->defaultSize;
241
242
45.7k
  for (size_t index = 0; index < pool->aSize; index++)
243
22.6k
  {
244
22.6k
    struct s_StreamPoolEntry* cur = &pool->aArray[index];
245
22.6k
    s = cur->s;
246
247
22.6k
    if (Stream_Capacity(s) >= size)
248
15.6k
    {
249
15.6k
      found = TRUE;
250
15.6k
      foundIndex = index;
251
15.6k
      break;
252
15.6k
    }
253
22.6k
  }
254
255
38.8k
  if (!found)
256
23.1k
  {
257
23.1k
    s = Stream_New(nullptr, size);
258
23.1k
    if (!s)
259
0
      goto out_fail;
260
23.1k
  }
261
15.6k
  else if (s)
262
15.6k
  {
263
15.6k
    Stream_ResetPosition(s);
264
15.6k
    if (!Stream_SetLength(s, Stream_Capacity(s)))
265
0
      goto out_fail;
266
15.6k
    StreamPool_ShiftAvailable(pool, foundIndex);
267
15.6k
  }
268
269
38.8k
  if (s)
270
38.8k
  {
271
38.8k
    s->pool = pool;
272
38.8k
    s->count = 1;
273
38.8k
    StreamPool_AddUsed(pool, s);
274
38.8k
  }
275
276
38.8k
out_fail:
277
38.8k
  StreamPool_Unlock(pool);
278
279
38.8k
  return s;
280
38.8k
}
281
282
/**
283
 * Returns an object to the pool.
284
 */
285
286
static void StreamPool_Remove(wStreamPool* pool, wStream* s)
287
38.8k
{
288
38.8k
  StreamPool_EnsureCapacity(pool, 1, FALSE);
289
38.8k
  Stream_EnsureValidity(s);
290
38.8k
  StreamPool_ShrinkToCommonLimit(s);
291
51.2k
  for (size_t x = 0; x < pool->aSize; x++)
292
12.4k
  {
293
12.4k
    wStream* cs = pool->aArray[x].s;
294
12.4k
    if (cs == s)
295
0
      return;
296
12.4k
  }
297
38.8k
  pool->aArray[(pool->aSize)++] = add_entry(s);
298
38.8k
  StreamPool_RemoveUsed(pool, s);
299
38.8k
}
300
301
static void StreamPool_ReleaseOrReturn(wStreamPool* pool, wStream* s)
302
38.8k
{
303
38.8k
  StreamPool_Lock(pool);
304
38.8k
  StreamPool_Remove(pool, s);
305
38.8k
  StreamPool_Unlock(pool);
306
38.8k
}
307
308
void StreamPool_Return(wStreamPool* pool, wStream* s)
309
0
{
310
0
  WINPR_ASSERT(pool);
311
0
  if (!s)
312
0
    return;
313
314
0
  StreamPool_Lock(pool);
315
0
  StreamPool_Remove(pool, s);
316
0
  StreamPool_Unlock(pool);
317
0
}
318
319
/**
320
 * Increment stream reference count
321
 */
322
323
void Stream_AddRef(wStream* s)
324
71
{
325
71
  WINPR_ASSERT(s);
326
71
  s->count++;
327
71
}
328
329
/**
330
 * Decrement stream reference count
331
 */
332
333
void Stream_Release(wStream* s)
334
47.9k
{
335
47.9k
  WINPR_ASSERT(s);
336
337
47.9k
  if (s->count > 0)
338
47.9k
    s->count--;
339
47.9k
  if (s->count == 0)
340
47.8k
  {
341
47.8k
    if (s->pool)
342
38.8k
      StreamPool_ReleaseOrReturn(s->pool, s);
343
9.02k
    else
344
9.02k
      Stream_Free(s, TRUE);
345
47.8k
  }
346
47.9k
}
347
348
/**
349
 * Find stream in pool using pointer inside buffer
350
 */
351
352
wStream* StreamPool_Find(wStreamPool* pool, const BYTE* ptr)
353
0
{
354
0
  wStream* s = nullptr;
355
356
0
  StreamPool_Lock(pool);
357
358
0
  for (size_t index = 0; index < pool->uSize; index++)
359
0
  {
360
0
    struct s_StreamPoolEntry* cur = &pool->uArray[index];
361
362
0
    if ((ptr >= Stream_Buffer(cur->s)) &&
363
0
        (ptr < (Stream_Buffer(cur->s) + Stream_Capacity(cur->s))))
364
0
    {
365
0
      s = cur->s;
366
0
      break;
367
0
    }
368
0
  }
369
370
0
  StreamPool_Unlock(pool);
371
372
0
  return s;
373
0
}
374
375
/**
376
 * Releases the streams currently cached in the pool.
377
 */
378
379
void StreamPool_Clear(wStreamPool* pool)
380
22.4k
{
381
22.4k
  StreamPool_Lock(pool);
382
383
45.5k
  for (size_t x = 0; x < pool->aSize; x++)
384
23.1k
  {
385
23.1k
    struct s_StreamPoolEntry* cur = &pool->aArray[x];
386
23.1k
    discard_entry(cur, TRUE);
387
23.1k
  }
388
22.4k
  pool->aSize = 0;
389
390
22.4k
  if (pool->uSize > 0)
391
0
  {
392
0
    WLog_Print(pool->log, WLOG_WARN,
393
0
               "Clearing StreamPool, but there are %" PRIuz " streams currently in use",
394
0
               pool->uSize);
395
0
    for (size_t x = 0; x < pool->uSize; x++)
396
0
    {
397
0
      struct s_StreamPoolEntry* cur = &pool->uArray[x];
398
0
      discard_entry(cur, TRUE);
399
0
    }
400
0
    pool->uSize = 0;
401
0
  }
402
403
22.4k
  StreamPool_Unlock(pool);
404
22.4k
}
405
406
size_t StreamPool_UsedCount(wStreamPool* pool)
407
21.4k
{
408
21.4k
  StreamPool_Lock(pool);
409
21.4k
  size_t usize = pool->uSize;
410
21.4k
  StreamPool_Unlock(pool);
411
21.4k
  return usize;
412
21.4k
}
413
414
/**
415
 * Construction, Destruction
416
 */
417
418
wStreamPool* StreamPool_New(BOOL synchronized, size_t defaultSize)
419
22.4k
{
420
22.4k
  wStreamPool* pool = calloc(1, sizeof(wStreamPool));
421
422
22.4k
  if (!pool)
423
0
    return nullptr;
424
425
22.4k
  pool->log = WLog_Create(XTAG, WLog_GetRoot());
426
22.4k
  if (!pool->log)
427
0
    goto fail;
428
429
22.4k
  pool->synchronized = synchronized;
430
22.4k
  pool->defaultSize = defaultSize;
431
432
22.4k
  if (!StreamPool_EnsureCapacity(pool, 32, FALSE))
433
0
    goto fail;
434
22.4k
  if (!StreamPool_EnsureCapacity(pool, 32, TRUE))
435
0
    goto fail;
436
437
22.4k
  if (!InitializeCriticalSectionAndSpinCount(&pool->lock, 4000))
438
0
    goto fail;
439
440
22.4k
  return pool;
441
0
fail:
442
0
  WINPR_PRAGMA_DIAG_PUSH
443
0
  WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC
444
0
  StreamPool_Free(pool);
445
0
  WINPR_PRAGMA_DIAG_POP
446
0
  return nullptr;
447
22.4k
}
448
449
void StreamPool_Free(wStreamPool* pool)
450
22.4k
{
451
22.4k
  if (!pool)
452
0
    return;
453
454
22.4k
  StreamPool_Clear(pool);
455
456
22.4k
  DeleteCriticalSection(&pool->lock);
457
458
22.4k
  free(pool->aArray);
459
22.4k
  free(pool->uArray);
460
461
22.4k
  WLog_Discard(pool->log);
462
22.4k
  free(pool);
463
22.4k
}
464
465
char* StreamPool_GetStatistics(wStreamPool* pool, char* buffer, size_t size)
466
0
{
467
0
  WINPR_ASSERT(pool);
468
469
0
  if (!buffer || (size < 1))
470
0
    return nullptr;
471
472
0
  size_t used = 0;
473
0
  int offset = _snprintf(buffer, size - 1,
474
0
                         "aSize    =%" PRIuz ", uSize    =%" PRIuz ", aCapacity=%" PRIuz
475
0
                         ", uCapacity=%" PRIuz,
476
0
                         pool->aSize, pool->uSize, pool->aCapacity, pool->uCapacity);
477
0
  if ((offset > 0) && ((size_t)offset < size))
478
0
    used += (size_t)offset;
479
480
0
#if defined(WITH_DEBUG_STREAMPOOL)
481
0
  StreamPool_Lock(pool);
482
483
0
  offset = _snprintf(&buffer[used], size - 1 - used, "\n-- dump used array take locations --\n");
484
0
  if ((offset > 0) && ((size_t)offset < size - used))
485
0
    used += (size_t)offset;
486
0
  for (size_t x = 0; x < pool->uSize; x++)
487
0
  {
488
0
    const struct s_StreamPoolEntry* cur = &pool->uArray[x];
489
0
    WINPR_ASSERT(cur->msg || (cur->lines == 0));
490
491
0
    for (size_t y = 0; y < cur->lines; y++)
492
0
    {
493
0
      offset = _snprintf(&buffer[used], size - 1 - used, "[%" PRIuz " | %" PRIuz "]: %s\n", x,
494
0
                         y, cur->msg[y]);
495
0
      if ((offset > 0) && ((size_t)offset < size - used))
496
0
        used += (size_t)offset;
497
0
    }
498
0
  }
499
500
0
  offset = _snprintf(&buffer[used], size - 1 - used, "\n-- statistics called from --\n");
501
0
  if ((offset > 0) && ((size_t)offset < size - used))
502
0
    used += (size_t)offset;
503
504
0
  struct s_StreamPoolEntry entry = WINPR_C_ARRAY_INIT;
505
0
  void* stack = winpr_backtrace(20);
506
0
  if (stack)
507
0
    entry.msg = winpr_backtrace_symbols(stack, &entry.lines);
508
0
  winpr_backtrace_free(stack);
509
510
0
  for (size_t x = 0; x < entry.lines; x++)
511
0
  {
512
0
    const char* msg = entry.msg[x];
513
0
    offset = _snprintf(&buffer[used], size - 1 - used, "[%" PRIuz "]: %s\n", x, msg);
514
0
    if ((offset > 0) && ((size_t)offset < size - used))
515
0
      used += (size_t)offset;
516
0
  }
517
0
  free((void*)entry.msg);
518
0
  StreamPool_Unlock(pool);
519
0
#endif
520
0
  buffer[used] = '\0';
521
0
  return buffer;
522
0
}
523
524
BOOL StreamPool_WaitForReturn(wStreamPool* pool, UINT32 timeoutMS)
525
21.4k
{
526
  /* HACK: We disconnected the transport above, now wait without a read or write lock until all
527
   * streams in use have been returned to the pool. */
528
21.4k
  while (timeoutMS > 0)
529
21.4k
  {
530
21.4k
    const size_t used = StreamPool_UsedCount(pool);
531
21.4k
    if (used == 0)
532
21.4k
      return TRUE;
533
0
    WLog_Print(pool->log, WLOG_DEBUG, "%" PRIuz " streams still in use, sleeping...", used);
534
535
0
    char buffer[4096] = WINPR_C_ARRAY_INIT;
536
0
    StreamPool_GetStatistics(pool, buffer, sizeof(buffer));
537
0
    WLog_Print(pool->log, WLOG_TRACE, "Pool statistics: %s", buffer);
538
539
0
    UINT32 diff = 10;
540
0
    if (timeoutMS != INFINITE)
541
0
    {
542
0
      diff = timeoutMS > 10 ? 10 : timeoutMS;
543
0
      timeoutMS -= diff;
544
0
    }
545
0
    Sleep(diff);
546
0
  }
547
548
0
  return FALSE;
549
21.4k
}