19
19
import java .util .Arrays ;
20
20
import java .util .concurrent .Semaphore ;
21
21
import java .util .concurrent .TimeUnit ;
22
- import java .util .concurrent .atomic .AtomicBoolean ;
22
+ import java .util .concurrent .atomic .AtomicInteger ;
23
23
import org .slf4j .Logger ;
24
24
import org .slf4j .LoggerFactory ;
25
25
import org .springframework .util .Assert ;
@@ -47,7 +47,7 @@ public class SemaphoreBackPressureHandler implements BatchAwareBackPressureHandl
47
47
48
48
private volatile CurrentThroughputMode currentThroughputMode ;
49
49
50
- private final AtomicBoolean hasAcquiredFullPermits = new AtomicBoolean ( false );
50
+ private final AtomicInteger lowThroughputPermitsAcquired = new AtomicInteger ( 0 );
51
51
52
52
private String id ;
53
53
@@ -79,42 +79,39 @@ public String getId() {
79
79
}
80
80
81
81
@ Override
82
- public int request (int amount ) throws InterruptedException {
83
- if (amount == batchSize ) {
84
- return requestBatch ();
85
- }
86
- return tryAcquire (amount , this .currentThroughputMode ) ? amount : 0 ;
82
+ public int requestBatch () throws InterruptedException {
83
+ return request (batchSize );
87
84
}
88
85
89
86
// @formatter:off
90
87
@ Override
91
- public int requestBatch ( ) throws InterruptedException {
88
+ public int request ( int amount ) throws InterruptedException {
92
89
return CurrentThroughputMode .LOW .equals (this .currentThroughputMode )
93
- ? requestInLowThroughputMode ()
94
- : requestInHighThroughputMode ();
90
+ ? requestInLowThroughputMode (amount )
91
+ : requestInHighThroughputMode (amount );
95
92
}
96
93
97
- private int requestInHighThroughputMode () throws InterruptedException {
98
- return tryAcquire (this . batchSize , CurrentThroughputMode .HIGH )
99
- ? this . batchSize
100
- : tryAcquirePartial ();
94
+ private int requestInHighThroughputMode (int amount ) throws InterruptedException {
95
+ return tryAcquire (amount , CurrentThroughputMode .HIGH )
96
+ ? amount
97
+ : tryAcquirePartial (amount );
101
98
}
102
99
// @formatter:on
103
100
104
- private int tryAcquirePartial () throws InterruptedException {
101
+ private int tryAcquirePartial (int max ) throws InterruptedException {
105
102
int availablePermits = this .semaphore .availablePermits ();
106
103
if (availablePermits == 0 || BackPressureMode .ALWAYS_POLL_MAX_MESSAGES .equals (this .backPressureConfiguration )) {
107
104
return 0 ;
108
105
}
109
- int permitsToRequest = Math .min (availablePermits , this . batchSize );
106
+ int permitsToRequest = Math .min (availablePermits , max );
110
107
CurrentThroughputMode currentThroughputModeNow = this .currentThroughputMode ;
111
108
logger .trace ("Trying to acquire partial batch of {} permits from {} available for {} in TM {}" ,
112
109
permitsToRequest , availablePermits , this .id , currentThroughputModeNow );
113
110
boolean hasAcquiredPartial = tryAcquire (permitsToRequest , currentThroughputModeNow );
114
111
return hasAcquiredPartial ? permitsToRequest : 0 ;
115
112
}
116
113
117
- private int requestInLowThroughputMode () throws InterruptedException {
114
+ private int requestInLowThroughputMode (int amount ) throws InterruptedException {
118
115
// Although LTM can be set / unset by many processes, only the MessageSource thread gets here,
119
116
// so no actual concurrency
120
117
logger .debug ("Trying to acquire full permits for {}. Permits left: {}" , this .id ,
@@ -123,11 +120,11 @@ private int requestInLowThroughputMode() throws InterruptedException {
123
120
if (hasAcquired ) {
124
121
logger .debug ("Acquired full permits for {}. Permits left: {}" , this .id , this .semaphore .availablePermits ());
125
122
// We've acquired all permits - there's no other process currently processing messages
126
- if (! this .hasAcquiredFullPermits . compareAndSet ( false , true ) ) {
123
+ if (this .lowThroughputPermitsAcquired . getAndSet ( amount ) != 0 ) {
127
124
logger .warn ("hasAcquiredFullPermits was already true. Permits left: {}" ,
128
125
this .semaphore .availablePermits ());
129
126
}
130
- return this . batchSize ;
127
+ return amount ;
131
128
}
132
129
else {
133
130
return 0 ;
@@ -150,19 +147,22 @@ private boolean tryAcquire(int amount, CurrentThroughputMode currentThroughputMo
150
147
}
151
148
152
149
@ Override
153
- public void releaseBatch () {
154
- maybeSwitchToLowThroughputMode ();
155
- int permitsToRelease = getPermitsToRelease (this .batchSize );
150
+ public void release (int amount , ReleaseReason reason ) {
151
+ logger .trace ("Releasing {} permits ({}) for {}. Permits left: {}" , amount , reason , this .id ,
152
+ this .semaphore .availablePermits ());
153
+ switch (reason ) {
154
+ case NONE_FETCHED -> maybeSwitchToLowThroughputMode ();
155
+ case PARTIAL_FETCH -> maybeSwitchToHighThroughputMode (amount );
156
+ case PROCESSED , LIMITED -> {
157
+ // No need to switch throughput mode
158
+ }
159
+ }
160
+ int permitsToRelease = getPermitsToRelease (amount );
156
161
this .semaphore .release (permitsToRelease );
157
- logger .trace ("Released {} permits for {}. Permits left: {}" , permitsToRelease , this .id ,
162
+ logger .debug ("Released {} permits ({}) for {}. Permits left: {}" , permitsToRelease , reason , this .id ,
158
163
this .semaphore .availablePermits ());
159
164
}
160
165
161
- @ Override
162
- public int getBatchSize () {
163
- return this .batchSize ;
164
- }
165
-
166
166
private void maybeSwitchToLowThroughputMode () {
167
167
if (!BackPressureMode .FIXED_HIGH_THROUGHPUT .equals (this .backPressureConfiguration )
168
168
&& CurrentThroughputMode .HIGH .equals (this .currentThroughputMode )) {
@@ -172,37 +172,23 @@ private void maybeSwitchToLowThroughputMode() {
172
172
}
173
173
}
174
174
175
- @ Override
176
- public void release ( int amount ) {
177
- if ( amount == batchSize ) {
178
- releaseBatch ( );
179
- return ;
175
+ private void maybeSwitchToHighThroughputMode ( int amount ) {
176
+ if ( CurrentThroughputMode . LOW . equals ( this . currentThroughputMode ) ) {
177
+ logger . debug ( "{} unused permit(s), setting TM HIGH for {}. Permits left: {}" , amount , this . id ,
178
+ this . semaphore . availablePermits () );
179
+ this . currentThroughputMode = CurrentThroughputMode . HIGH ;
180
180
}
181
- logger .trace ("Releasing {} permits for {}. Permits left: {}" , amount , this .id ,
182
- this .semaphore .availablePermits ());
183
- maybeSwitchToHighThroughputMode (amount );
184
- int permitsToRelease = getPermitsToRelease (amount );
185
- this .semaphore .release (permitsToRelease );
186
- logger .trace ("Released {} permits for {}. Permits left: {}" , permitsToRelease , this .id ,
187
- this .semaphore .availablePermits ());
188
181
}
189
182
190
183
private int getPermitsToRelease (int amount ) {
191
- return this .hasAcquiredFullPermits .compareAndSet (true , false )
184
+ int lowThroughputPermits = this .lowThroughputPermitsAcquired .getAndSet (0 );
185
+ return lowThroughputPermits > 0
192
186
// The first process that gets here should release all permits except for inflight messages
193
187
// We can have only one batch of messages at this point since we have all permits
194
- ? this .totalPermits - (this . batchSize - amount )
188
+ ? this .totalPermits - (lowThroughputPermits - amount )
195
189
: amount ;
196
190
}
197
191
198
- private void maybeSwitchToHighThroughputMode (int amount ) {
199
- if (CurrentThroughputMode .LOW .equals (this .currentThroughputMode )) {
200
- logger .debug ("{} unused permit(s), setting TM HIGH for {}. Permits left: {}" , amount , this .id ,
201
- this .semaphore .availablePermits ());
202
- this .currentThroughputMode = CurrentThroughputMode .HIGH ;
203
- }
204
- }
205
-
206
192
@ Override
207
193
public boolean drain (Duration timeout ) {
208
194
logger .debug ("Waiting for up to {} seconds for approx. {} permits to be released for {}" , timeout .getSeconds (),
0 commit comments