Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Take advantage of Java 5 language features #389

Merged
merged 3 commits into from
Oct 15, 2020
Merged

Conversation

basil
Copy link
Member

@basil basil commented Jul 2, 2020

Downstream of #388. This PR updates the code base to take full advantage of Java 5 language features; namely, enhanced for and generics. There are two commits in this change (on top of the commit from #388). I suggest reviewing each commit separately. The first commit should be fairly self-explanatory and replaces any for loops with Java 5 enhanced for. The second commit adds generics to any types that were missing them and also tightens the bounds of these generics. I will explain these changes in a review.

This change may appear risky at first glance but it is actually completely safe due to the fact that no runtime code is affected. Yes, type parameters have been changed, but they only affect compilation and not runtime due to type erasure. There are only two places where I introduced casts in non-test code, but those casts are safe because they only cast from one set of type parameters to another set of type parameters (not from one type of object to another type of object). In other words, there is no runtime impact whatsoever.

@@ -360,7 +360,7 @@ boolean isRecording() {
synchronized <T> int export(@Nonnull Class<T> clazz, @CheckForNull T t, boolean notifyListener) {
if(t==null) return 0; // bootstrap classloader

Entry e = reverse.get(t);
Entry<T> e = (Entry<T>) reverse.get(t);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This cast is safe, as can be seen by reading the surrounding code that places entries into the map. The keys of the map are of type Object and the values are of type Entry. The only time that we ever add something to the map, the Object is parameterized on T and the Entry is also parameterized on the same T.

@@ -79,7 +79,7 @@
*/
private int lastIoId;

private volatile Response<RSP,EXC> response;
private volatile Response<RSP,? extends Throwable> response;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The original type parameter was wrong. Fortunately, this never resulted in any actual bugs due to type erasure. The reasoning is as follows. The EXC type parameter in the Request class represents the bound for the exception types that subclasses of Request may produce. The Response class may include exceptions of these types, but it may also include an exception of type RequestAbortedException (see Request#abort(IOException)). Recall that this field is in the Request class, so EXC represents the bound on the request's exception types, not the response's exception types. To keep supporting Request#abort(IOException), the bound here must be able to include RequestAbortedException. So the bound for EXC is not large enough, so we must include all instances of Throwable. This also prevents us from having to make a risky change to the Runnable in Request#execute.

@@ -192,10 +192,10 @@ final RSP call(Channel channel) throws EXC, InterruptedException, IOException {
// ignore the I/O error
}

Object exc = response.exception;
Throwable exc = response.exception;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

response.exception was always of type Throwable, so this was just unnecessary.

@@ -341,7 +341,7 @@ public RSP get(long timeout, TimeUnit unit) throws InterruptedException, Executi
* Aborts the processing. The calling thread will receive an exception.
*/
/*package*/ void abort(IOException e) {
onCompleted(new Response(this, id, 0, new RequestAbortedException(e)));
onCompleted(new Response<>(this, id, 0, new RequestAbortedException(e)));
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change would not have worked if we had not expanded the bound above to be able to include RequestAbortedException.

@@ -36,7 +37,7 @@
* @see Request
* @since 3.17
*/
public final class Response<RSP,EXC extends Throwable> extends Command {
public final class Response<RSP extends Serializable,EXC extends Throwable> extends Command {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instances of Response are always created from instances of Request, whose RSP type parameter is bounded by Serializable. So there is no reason not to similarly bound the type parameter of Response. Doing so makes the relationship between the two more clear. Note that the bound of the EXC type parameter on the Response class may be different (broader) than that of the Request class due to the fact that a response may include a RequestAbortedException as described above.

@@ -59,17 +60,17 @@
@SuppressFBWarnings(value="SE_TRANSIENT_FIELD_NOT_RESTORED", justification="Only supposed to be defined on one side.")
private transient long totalTime;
@SuppressFBWarnings(value="SE_TRANSIENT_FIELD_NOT_RESTORED", justification="Bound after deserialization, in execute.")
private transient Request<?, ?> request;
private transient Request<RSP, ? extends Throwable> request;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As in Request, we cannot use EXC here because the bound for the Response exception must be larger than those of the Request exception.

@@ -82,7 +83,7 @@
*/
@Override
void execute(Channel channel) {
Request req = channel.pendingCalls.get(id);
Request<RSP, ? extends Throwable> req = (Request<RSP, ? extends Throwable>) channel.pendingCalls.get(id);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This cast is safe because the RSP type parameter shares the same bounds for a Request and its Response.

@basil
Copy link
Member Author

basil commented Jul 2, 2020

My CI run "failed" in the Windows branch, but this failure is spurious because the mvn(1) command actually succeeded. The overall branch was erroneously marked as failed due to a Remoting exception. 😉

@jeffret-b
Copy link
Contributor

Let's try running the CI again.

@jeffret-b jeffret-b closed this Jul 2, 2020
@jeffret-b jeffret-b reopened this Jul 2, 2020
@jeffret-b
Copy link
Contributor

Build now passes. I'll take a look at the proposal when I get the chance.

Copy link
Contributor

@jeffret-b jeffret-b left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice improvements. This will help the maintainability and readability.

Thanks for the submission.

@jeffret-b jeffret-b added chore For changelog: A maintenance chore with no functional changes ready-to-merge labels Oct 14, 2020
@basil
Copy link
Member Author

basil commented Oct 14, 2020

Thanks for the submission.

Thank you for the review!

@jeffret-b jeffret-b merged commit 68c21f7 into jenkinsci:master Oct 15, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
chore For changelog: A maintenance chore with no functional changes ready-to-merge
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants