Skip to content

Commit 06216fd

Browse files
authored
Merge pull request groue#1225 from groue/dev/thread-limit
Prevent heavy DatabasePool concurrent reads from creating too many threads
2 parents aaf3efe + 42050e5 commit 06216fd

File tree

12 files changed

+261
-42
lines changed

12 files changed

+261
-42
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,10 @@ GRDB adheres to [Semantic Versioning](https://semver.org/), with one exception:
9191

9292
---
9393

94+
## Next Release
95+
96+
- **Fixed**: [#1225](https://github.com/groue/GRDB.swift/pull/1225) by [@groue](https://github.com/groue): Prevent heavy DatabasePool concurrent reads from creating too many threads
97+
9498
## 5.24.0
9599

96100
Released May 1, 2022 • [diff](https://github.com/groue/GRDB.swift/compare/v5.23.0...v5.24.0)

GRDB.xcodeproj/project.pbxproj

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -924,6 +924,9 @@
924924
56F5ABDA1D814330001F60CB /* NSData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5657AAB81D107001006283EF /* NSData.swift */; };
925925
56F5ABDC1D814330001F60CB /* URL.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5657AB0E1D10899D006283EF /* URL.swift */; };
926926
56F5ABDD1D814330001F60CB /* UUID.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56A8C22F1D1914540096E9D4 /* UUID.swift */; };
927+
56F61DD5283D344E00AF9884 /* getThreadsCount.c in Sources */ = {isa = PBXBuildFile; fileRef = 56F61DD4283D344E00AF9884 /* getThreadsCount.c */; };
928+
56F61DD6283D344E00AF9884 /* getThreadsCount.c in Sources */ = {isa = PBXBuildFile; fileRef = 56F61DD4283D344E00AF9884 /* getThreadsCount.c */; };
929+
56F61DD7283D344E00AF9884 /* getThreadsCount.c in Sources */ = {isa = PBXBuildFile; fileRef = 56F61DD4283D344E00AF9884 /* getThreadsCount.c */; };
927930
56FBFED92210731A00945324 /* SQLRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56FBFED82210731A00945324 /* SQLRequest.swift */; };
928931
56FBFEDA2210731A00945324 /* SQLRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56FBFED82210731A00945324 /* SQLRequest.swift */; };
929932
56FBFEDB2210731A00945324 /* SQLRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56FBFED82210731A00945324 /* SQLRequest.swift */; };
@@ -1646,6 +1649,9 @@
16461649
56F34FB924B094B6007513FC /* SQLExpressionIsConstantTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SQLExpressionIsConstantTests.swift; sourceTree = "<group>"; };
16471650
56F34FC124B0A0B7007513FC /* SQLIdentifyingColumnsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SQLIdentifyingColumnsTests.swift; sourceTree = "<group>"; };
16481651
56F3E7481E66F83A00BF0F01 /* ResultCodeTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ResultCodeTests.swift; sourceTree = "<group>"; };
1652+
56F61DD0283D344D00AF9884 /* GRDBTests-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "GRDBTests-Bridging-Header.h"; path = "GRDBTests/GRDBTests-Bridging-Header.h"; sourceTree = "<group>"; };
1653+
56F61DD3283D344E00AF9884 /* getThreadsCount.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = getThreadsCount.h; sourceTree = "<group>"; };
1654+
56F61DD4283D344E00AF9884 /* getThreadsCount.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = getThreadsCount.c; sourceTree = "<group>"; };
16491655
56FBFED82210731A00945324 /* SQLRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SQLRequest.swift; sourceTree = "<group>"; };
16501656
56FDECE11BB32DFD009AD709 /* RowFromStatementTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RowFromStatementTests.swift; sourceTree = "<group>"; };
16511657
56FEB8F6248402E10081AF83 /* DatabaseTraceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DatabaseTraceTests.swift; sourceTree = "<group>"; };
@@ -1796,6 +1802,8 @@
17961802
569531361C919DF700CF1A2B /* DatabasePoolFunctionTests.swift */,
17971803
56EA869D1C932597002BB4DF /* DatabasePoolReadOnlyTests.swift */,
17981804
563C67B224628BEA00E94EDC /* DatabasePoolTests.swift */,
1805+
56F61DD3283D344E00AF9884 /* getThreadsCount.h */,
1806+
56F61DD4283D344E00AF9884 /* getThreadsCount.c */,
17991807
);
18001808
name = DatabasePool;
18011809
sourceTree = "<group>";
@@ -2502,6 +2510,7 @@
25022510
DC37740319C8CBB3004FCF85 /* Supporting Files */ = {
25032511
isa = PBXGroup;
25042512
children = (
2513+
56F61DD0283D344D00AF9884 /* GRDBTests-Bridging-Header.h */,
25052514
DC37740419C8CBB3004FCF85 /* Info.plist */,
25062515
);
25072516
name = "Supporting Files";
@@ -2752,17 +2761,18 @@
27522761
};
27532762
56E5D7D21B4D3FEE00430942 = {
27542763
CreatedOnToolsVersion = 7.0;
2755-
LastSwiftMigration = 1130;
2764+
LastSwiftMigration = 1340;
27562765
ProvisioningStyle = Manual;
27572766
};
27582767
56E5D7F81B4D422D00430942 = {
27592768
CreatedOnToolsVersion = 7.0;
2760-
LastSwiftMigration = 1020;
2769+
LastSwiftMigration = 1340;
27612770
};
27622771
AAA4DC75230F1E0600C74B15 = {
27632772
ProvisioningStyle = Manual;
27642773
};
27652774
AAA4DD07230F262000C74B15 = {
2775+
LastSwiftMigration = 1340;
27662776
ProvisioningStyle = Manual;
27672777
};
27682778
DC3773F219C8CBB3004FCF85 = {
@@ -3432,6 +3442,7 @@
34323442
56A2383A1B9C74A90082EB20 /* DatabaseQueueInMemoryTests.swift in Sources */,
34333443
56A5E40A1BA2BCF900707640 /* RecordWithColumnNameManglingTests.swift in Sources */,
34343444
56677C1A241D217F0050755D /* ValueObservationRecorderTests.swift in Sources */,
3445+
56F61DD6283D344E00AF9884 /* getThreadsCount.c in Sources */,
34353446
5653EAF520944B4F00F46237 /* AssociationParallelSQLTests.swift in Sources */,
34363447
563B06BE2185CCD300B38F35 /* ValueObservationTests.swift in Sources */,
34373448
56FF455A1D2CDA5200F21EF9 /* RecordUniqueIndexTests.swift in Sources */,
@@ -3667,6 +3678,7 @@
36673678
56DF001D228DDBA300D611F3 /* AssociationPrefetchingCodableRecordTests.swift in Sources */,
36683679
5653EAD820944B4F00F46237 /* AssociationBelongsToRowScopeTests.swift in Sources */,
36693680
562205F11E420E47005860AC /* DatabasePoolReleaseMemoryTests.swift in Sources */,
3681+
56F61DD5283D344E00AF9884 /* getThreadsCount.c in Sources */,
36703682
56677C19241D217F0050755D /* ValueObservationRecorderTests.swift in Sources */,
36713683
5653EAF420944B4F00F46237 /* AssociationParallelSQLTests.swift in Sources */,
36723684
563B06BD2185CCD300B38F35 /* ValueObservationTests.swift in Sources */,
@@ -4038,6 +4050,7 @@
40384050
AAA4DDC0230F262000C74B15 /* DatabaseQueueInMemoryTests.swift in Sources */,
40394051
AAA4DDC1230F262000C74B15 /* RecordWithColumnNameManglingTests.swift in Sources */,
40404052
56677C1B241D217F0050755D /* ValueObservationRecorderTests.swift in Sources */,
4053+
56F61DD7283D344E00AF9884 /* getThreadsCount.c in Sources */,
40414054
AAA4DDC2230F262000C74B15 /* AssociationParallelSQLTests.swift in Sources */,
40424055
AAA4DDC3230F262000C74B15 /* ValueObservationTests.swift in Sources */,
40434056
AAA4DDC4230F262000C74B15 /* RecordUniqueIndexTests.swift in Sources */,
@@ -4382,6 +4395,7 @@
43824395
isa = XCBuildConfiguration;
43834396
baseConfigurationReference = 56C494401ED7255500CC72AF /* GRDBDeploymentTarget.xcconfig */;
43844397
buildSettings = {
4398+
CLANG_ENABLE_MODULES = YES;
43854399
CLANG_ENABLE_OBJC_WEAK = YES;
43864400
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
43874401
CODE_SIGN_STYLE = Manual;
@@ -4400,13 +4414,17 @@
44004414
PROVISIONING_PROFILE_SPECIFIER = "";
44014415
"PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = "";
44024416
SDKROOT = iphoneos;
4417+
SWIFT_OBJC_BRIDGING_HEADER = "Tests/GRDBTests/GRDBTests-Bridging-Header.h";
4418+
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
4419+
SWIFT_VERSION = 5.0;
44034420
};
44044421
name = Debug;
44054422
};
44064423
56E5D7E01B4D3FEE00430942 /* Release */ = {
44074424
isa = XCBuildConfiguration;
44084425
baseConfigurationReference = 56C494401ED7255500CC72AF /* GRDBDeploymentTarget.xcconfig */;
44094426
buildSettings = {
4427+
CLANG_ENABLE_MODULES = YES;
44104428
CLANG_ENABLE_OBJC_WEAK = YES;
44114429
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
44124430
CODE_SIGN_STYLE = Manual;
@@ -4427,14 +4445,17 @@
44274445
"PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = "";
44284446
SDKROOT = iphoneos;
44294447
SWIFT_COMPILATION_MODE = wholemodule;
4448+
SWIFT_OBJC_BRIDGING_HEADER = "Tests/GRDBTests/GRDBTests-Bridging-Header.h";
44304449
SWIFT_OPTIMIZATION_LEVEL = "-O";
4450+
SWIFT_VERSION = 5.0;
44314451
};
44324452
name = Release;
44334453
};
44344454
56E5D8021B4D422E00430942 /* Debug */ = {
44354455
isa = XCBuildConfiguration;
44364456
baseConfigurationReference = 56C494401ED7255500CC72AF /* GRDBDeploymentTarget.xcconfig */;
44374457
buildSettings = {
4458+
CLANG_ENABLE_MODULES = YES;
44384459
CLANG_ENABLE_OBJC_WEAK = YES;
44394460
COMBINE_HIDPI_IMAGES = YES;
44404461
DEBUG_INFORMATION_FORMAT = dwarf;
@@ -4449,13 +4470,17 @@
44494470
PRODUCT_BUNDLE_IDENTIFIER = com.github.groue.GRDBOSXTests;
44504471
PRODUCT_NAME = "$(TARGET_NAME)";
44514472
SDKROOT = macosx;
4473+
SWIFT_OBJC_BRIDGING_HEADER = "Tests/GRDBTests/GRDBTests-Bridging-Header.h";
4474+
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
4475+
SWIFT_VERSION = 5.0;
44524476
};
44534477
name = Debug;
44544478
};
44554479
56E5D8031B4D422E00430942 /* Release */ = {
44564480
isa = XCBuildConfiguration;
44574481
baseConfigurationReference = 56C494401ED7255500CC72AF /* GRDBDeploymentTarget.xcconfig */;
44584482
buildSettings = {
4483+
CLANG_ENABLE_MODULES = YES;
44594484
CLANG_ENABLE_OBJC_WEAK = YES;
44604485
COMBINE_HIDPI_IMAGES = YES;
44614486
COPY_PHASE_STRIP = NO;
@@ -4472,7 +4497,9 @@
44724497
PRODUCT_NAME = "$(TARGET_NAME)";
44734498
SDKROOT = macosx;
44744499
SWIFT_COMPILATION_MODE = wholemodule;
4500+
SWIFT_OBJC_BRIDGING_HEADER = "Tests/GRDBTests/GRDBTests-Bridging-Header.h";
44754501
SWIFT_OPTIMIZATION_LEVEL = "-O";
4502+
SWIFT_VERSION = 5.0;
44764503
};
44774504
name = Release;
44784505
};
@@ -4544,6 +4571,7 @@
45444571
isa = XCBuildConfiguration;
45454572
baseConfigurationReference = 56C494401ED7255500CC72AF /* GRDBDeploymentTarget.xcconfig */;
45464573
buildSettings = {
4574+
CLANG_ENABLE_MODULES = YES;
45474575
CLANG_ENABLE_OBJC_WEAK = YES;
45484576
CODE_SIGN_IDENTITY = "iPhone Developer";
45494577
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
@@ -4562,13 +4590,17 @@
45624590
PRODUCT_NAME = "$(TARGET_NAME)";
45634591
PROVISIONING_PROFILE_SPECIFIER = "";
45644592
SDKROOT = appletvos;
4593+
SWIFT_OBJC_BRIDGING_HEADER = "Tests/GRDBTests/GRDBTests-Bridging-Header.h";
4594+
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
4595+
SWIFT_VERSION = 5.0;
45654596
};
45664597
name = Debug;
45674598
};
45684599
AAA4DDD3230F262000C74B15 /* Release */ = {
45694600
isa = XCBuildConfiguration;
45704601
baseConfigurationReference = 56C494401ED7255500CC72AF /* GRDBDeploymentTarget.xcconfig */;
45714602
buildSettings = {
4603+
CLANG_ENABLE_MODULES = YES;
45724604
CLANG_ENABLE_OBJC_WEAK = YES;
45734605
CODE_SIGN_IDENTITY = "iPhone Developer";
45744606
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
@@ -4589,7 +4621,9 @@
45894621
PROVISIONING_PROFILE_SPECIFIER = "";
45904622
SDKROOT = appletvos;
45914623
SWIFT_COMPILATION_MODE = wholemodule;
4624+
SWIFT_OBJC_BRIDGING_HEADER = "Tests/GRDBTests/GRDBTests-Bridging-Header.h";
45924625
SWIFT_OPTIMIZATION_LEVEL = "-O";
4626+
SWIFT_VERSION = 5.0;
45934627
};
45944628
name = Release;
45954629
};

GRDB/Core/DatabasePool.swift

Lines changed: 20 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -330,22 +330,14 @@ extension DatabasePool: DatabaseReader {
330330
}
331331

332332
public func asyncRead(_ value: @escaping (Result<Database, Error>) -> Void) {
333-
// First async jump in order to grab a reader connection.
334-
// Honor configuration dispatching (qos/targetQueue).
335-
let label = configuration.identifier(
336-
defaultLabel: "GRDB.DatabasePool",
337-
purpose: "asyncRead")
338-
configuration
339-
.makeReaderDispatchQueue(label: label)
340-
.async {
333+
do {
334+
guard let readerPool = self.readerPool else {
335+
throw DatabaseError(resultCode: .SQLITE_MISUSE, message: "Connection is closed")
336+
}
337+
readerPool.async { result in
341338
do {
342-
guard let readerPool = self.readerPool else {
343-
throw DatabaseError.connectionIsClosed()
344-
}
345-
let (reader, releaseReader) = try readerPool.get()
346-
347-
// Second async jump because sync could deadlock if
348-
// configuration has a serial targetQueue.
339+
let (reader, releaseReader) = try result.get()
340+
// Second async jump because that's how `Pool.async` has to be used.
349341
reader.async { db in
350342
defer {
351343
try? db.commit() // Ignore commit error
@@ -364,6 +356,9 @@ extension DatabasePool: DatabaseReader {
364356
value(.failure(error))
365357
}
366358
}
359+
} catch {
360+
value(.failure(error))
361+
}
367362
}
368363

369364
@_disfavoredOverload // SR-15150 Async overloading in protocol implementation fails
@@ -381,22 +376,14 @@ extension DatabasePool: DatabaseReader {
381376
}
382377

383378
public func asyncUnsafeRead(_ value: @escaping (Result<Database, Error>) -> Void) {
384-
// First async jump in order to grab a reader connection.
385-
// Honor configuration dispatching (qos/targetQueue).
386-
let label = configuration.identifier(
387-
defaultLabel: "GRDB.DatabasePool",
388-
purpose: "asyncUnsafeRead")
389-
configuration
390-
.makeReaderDispatchQueue(label: label)
391-
.async {
379+
do {
380+
guard let readerPool = self.readerPool else {
381+
throw DatabaseError(resultCode: .SQLITE_MISUSE, message: "Connection is closed")
382+
}
383+
readerPool.async { result in
392384
do {
393-
guard let readerPool = self.readerPool else {
394-
throw DatabaseError(resultCode: .SQLITE_MISUSE, message: "Connection is closed")
395-
}
396-
let (reader, releaseReader) = try readerPool.get()
397-
398-
// Second async jump because sync could deadlock if
399-
// configuration has a serial targetQueue.
385+
let (reader, releaseReader) = try result.get()
386+
// Second async jump because that's how `Pool.async` has to be used.
400387
reader.async { db in
401388
defer {
402389
releaseReader()
@@ -413,6 +400,9 @@ extension DatabasePool: DatabaseReader {
413400
value(.failure(error))
414401
}
415402
}
403+
} catch {
404+
value(.failure(error))
405+
}
416406
}
417407

418408
public func unsafeReentrantRead<T>(_ value: (Database) throws -> T) throws -> T {

GRDB/Utils/Pool.swift

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,13 +51,15 @@ final class Pool<T> {
5151
private let itemsSemaphore: DispatchSemaphore // limits the number of elements
5252
private let itemsGroup: DispatchGroup // knows when no element is used
5353
private let barrierQueue: DispatchQueue
54+
private let semaphoreWaitingQueue: DispatchQueue // Inspired by https://khanlou.com/2016/04/the-GCD-handbook/
5455

5556
init(maximumCount: Int, makeElement: @escaping () throws -> T) {
5657
GRDBPrecondition(maximumCount > 0, "Pool size must be at least 1")
5758
self.makeElement = makeElement
5859
self.itemsSemaphore = DispatchSemaphore(value: maximumCount)
5960
self.itemsGroup = DispatchGroup()
6061
self.barrierQueue = DispatchQueue(label: "GRDB.Pool.barrier", attributes: [.concurrent])
62+
self.semaphoreWaitingQueue = DispatchQueue(label: "GRDB.Pool.wait")
6163
}
6264

6365
/// Returns a tuple (element, release)
@@ -87,6 +89,25 @@ final class Pool<T> {
8789
}
8890
}
8991

92+
/// Eventually produces a tuple (element, release), where element is
93+
/// intended to be used asynchronously.
94+
///
95+
/// Client must call release(), only once, after the element has been used.
96+
///
97+
/// - important: The `execute` argument is executed in a serial dispatch
98+
/// queue, so make sure you use the element asynchronously.
99+
func async(_ execute: @escaping (Result<(element: T, release: () -> Void), Error>) -> Void) {
100+
// Inspired by https://khanlou.com/2016/04/the-GCD-handbook/
101+
// > We wait on the semaphore in the serial queue, which means that
102+
// > we’ll have at most one blocked thread when we reach maximum
103+
// > executing blocks on the concurrent queue. Any other tasks the user
104+
// > enqueues will sit inertly on the serial queue waiting to be
105+
// > executed, and won’t cause new threads to be started.
106+
semaphoreWaitingQueue.async {
107+
execute(Result { try self.get() })
108+
}
109+
}
110+
90111
/// Performs a synchronous block with an element. The element turns
91112
/// available after the block has executed.
92113
func get<U>(block: (T) throws -> U) throws -> U {

0 commit comments

Comments
 (0)