Skip to content

Commit 9fc32f1

Browse files
authored
binder: Add LeakSafeOneWayBinder and tests. (#8021)
Another util class, this one with tests which need to run on an Android emulator.
1 parent 11c3667 commit 9fc32f1

File tree

5 files changed

+169
-2
lines changed

5 files changed

+169
-2
lines changed

binder/build.gradle

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ dependencies {
4040
exclude group: 'com.google.auto.service', module: 'auto-service'
4141
}
4242
testImplementation libraries.truth
43+
androidTestImplementation libraries.androidx_test_rules
44+
androidTestImplementation libraries.androidx_test_ext_junit
45+
androidTestImplementation libraries.truth
4346
}
44-
4547
[publishMavenPublicationToMavenRepository]*.onlyIf { false }
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
/*
2+
* Copyright 2020 The gRPC Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.grpc.binder.internal;
18+
19+
import static com.google.common.truth.Truth.assertThat;
20+
21+
import android.os.Parcel;
22+
import androidx.test.ext.junit.runners.AndroidJUnit4;
23+
import org.junit.Before;
24+
import org.junit.Test;
25+
import org.junit.runner.RunWith;
26+
27+
@RunWith(AndroidJUnit4.class)
28+
public final class LeakSafeOneWayBinderTest {
29+
30+
private LeakSafeOneWayBinder binder;
31+
32+
private int transactionsHandled;
33+
private int lastCode;
34+
private Parcel lastParcel;
35+
36+
@Before
37+
public void setUp() {
38+
binder = new LeakSafeOneWayBinder((code, parcel) -> {
39+
transactionsHandled++;
40+
lastCode = code;
41+
lastParcel = parcel;
42+
return true;
43+
});
44+
}
45+
46+
@Test
47+
public void testTransaction() {
48+
Parcel p = Parcel.obtain();
49+
assertThat(binder.onTransact(123, p, null, 0)).isTrue();
50+
assertThat(transactionsHandled).isEqualTo(1);
51+
assertThat(lastCode).isEqualTo(123);
52+
assertThat(lastParcel).isSameInstanceAs(p);
53+
p.recycle();
54+
}
55+
56+
@Test
57+
public void testDetach() {
58+
Parcel p = Parcel.obtain();
59+
binder.detach();
60+
assertThat(binder.onTransact(456, p, null, 0)).isFalse();
61+
62+
// The transaction shouldn't have been processed.
63+
assertThat(transactionsHandled).isEqualTo(0);
64+
65+
p.recycle();
66+
}
67+
68+
@Test
69+
public void testMultipleTransactions() {
70+
Parcel p = Parcel.obtain();
71+
assertThat(binder.onTransact(123, p, null, 0)).isTrue();
72+
assertThat(binder.onTransact(456, p, null, 0)).isTrue();
73+
assertThat(transactionsHandled).isEqualTo(2);
74+
assertThat(lastCode).isEqualTo(456);
75+
assertThat(lastParcel).isSameInstanceAs(p);
76+
p.recycle();
77+
}
78+
79+
@Test
80+
public void testPing() {
81+
assertThat(binder.pingBinder()).isTrue();
82+
}
83+
84+
@Test
85+
public void testPing_Detached() {
86+
binder.detach();
87+
assertThat(binder.pingBinder()).isFalse();
88+
}
89+
}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
/*
2+
* Copyright 2020 The gRPC Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.grpc.binder.internal;
18+
19+
import android.os.Binder;
20+
import android.os.Parcel;
21+
import java.util.logging.Level;
22+
import java.util.logging.Logger;
23+
import javax.annotation.Nullable;
24+
25+
/**
26+
* An extension of {@link Binder} which delegates all transactions to an internal handler, and only
27+
* supports one way transactions.
28+
*
29+
* <p>Since Binder objects can be anchored forever by a misbehaved remote process, any references
30+
* they hold can lead to significant memory leaks. To prevent that, the {@link #detach} method
31+
* clears the reference to the delegate handler, ensuring that only this simple class is leaked.
32+
* Once detached, this binder returns false from any future transactions (and pings).
33+
*
34+
* <p>Since two-way transactions block the calling thread on a remote process, this class only
35+
* supports one-way calls.
36+
*/
37+
final class LeakSafeOneWayBinder extends Binder {
38+
39+
private static final Logger logger = Logger.getLogger(LeakSafeOneWayBinder.class.getName());
40+
41+
interface TransactionHandler {
42+
boolean handleTransaction(int code, Parcel data);
43+
}
44+
45+
@Nullable private TransactionHandler handler;
46+
47+
LeakSafeOneWayBinder(TransactionHandler handler) {
48+
this.handler = handler;
49+
}
50+
51+
void detach() {
52+
handler = null;
53+
}
54+
55+
@Override
56+
protected boolean onTransact(int code, Parcel parcel, Parcel reply, int flags) {
57+
TransactionHandler handler = this.handler;
58+
if (handler != null) {
59+
try {
60+
return handler.handleTransaction(code, parcel);
61+
} catch (RuntimeException re) {
62+
logger.log(Level.WARNING, "failure sending transaction " + code, re);
63+
return false;
64+
}
65+
} else {
66+
return false;
67+
}
68+
}
69+
70+
@Override
71+
public boolean pingBinder() {
72+
return handler != null;
73+
}
74+
}

binder/src/test/java/io/grpc/binder/internal/ServiceBindingTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
import androidx.test.core.app.ApplicationProvider;
3333
import io.grpc.Status;
3434
import io.grpc.Status.Code;
35-
import io.grpc.binder.util.Bindable.Observer;
35+
import io.grpc.binder.internal.Bindable.Observer;
3636
import org.junit.Before;
3737
import org.junit.Rule;
3838
import org.junit.Test;

build.gradle

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,8 @@ subprojects {
191191
androidx_annotation: "androidx.annotation:annotation:1.1.0",
192192
androidx_core: "androidx.core:core:1.3.0",
193193
androidx_test: "androidx.test:core:1.3.0",
194+
androidx_test_rules: "androidx.test:rules:1.3.0",
195+
androidx_test_ext_junit: "androidx.test.ext:junit:1.1.2",
194196
robolectric: "org.robolectric:robolectric:4.4",
195197

196198
// Benchmark dependencies

0 commit comments

Comments
 (0)