-
Notifications
You must be signed in to change notification settings - Fork 97
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
SharedFileInputStream should comply with spec #695
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@lukasj The main issue with this change is that there are finalizers declared in SharedFileInputStream and SharedFile. Should the change really be to just remove the finalizer methods as a slightly incompatible change? Or do we need to convert that code to the Cleaner API?
Really I think the SharedFileInputStream finalizer is mostly pointless since there is a finalizer on SharedFile where actual native resource exists. The only corner case of having a finalizer/cleaner on SharedFileInputStream would be in the case there is a 3rd party subclass that overrides close would see an invocation on G.C.
Makes me think that we should consider either removing both finalizers and document the behavior change. Or remove both finalizers and add a Cleaner API to just SharedFile. Then this patch could be redone such that we don't need to hold a reference to the root SharedFileInputStream.
@SeongEon-Jo You'll have to include a test case with this PR. Modify the hardcoded file path to use File.createTempFile. I'm thinking this test will fail even under the new patch.
|
@jmehrens I added a test case but slightly changed the code you gave because So I just casted it to Test result was "passed". Also, I ran the same test in junit5 like below.
Result was also "passed". I only added a case of grand child. But if you want me to add another cases, feel free to tell me about that. |
Oh I see. The reason this passes is that the un-reference parent gets garbage collected but the call to close by the finializer is a no-op because of the reference counting. All good then. You should add your original test case too as a method to the JUnit test. You should copy one of the license headers from the other test cases. |
Added the license header and child test case. But I want to notify that in the version before this patch, the test results were different depending on whether I invoke For example, in a test code I gave in #694,
it failed, which means But when I tested with the code below,
it passed, which means I guess this occurs because of the code scope but not sure of the exact reason. Of course, after my patch, no matter whether I invoke The reason I am notifying this is because I am not that sure if the test code I've added properly covers all the problematic situations. Please let me know your opinion and suggest test cases I can add to cover them. |
Put a comment on this behavior so future maintainers don't try to refactor and break the test. |
api/src/test/java/jakarta/mail/util/SharedFileInputStreamTest.java
Outdated
Show resolved
Hide resolved
api/src/test/java/jakarta/mail/util/SharedFileInputStreamTest.java
Outdated
Show resolved
Hide resolved
Added a comment to the test class. Feel free to tell me if there is something to add or edit in the comment. |
@SeongEon-Jo I've been looking at the source code and I think the original behavior of this class is by design. Per:
That is why there are 2 code paths for closing and force closing. So in theory this issue could be closed as will not fix because the behavior is actually documented. If we were going to change the behavior so this didn't happen then we could instead pinning the root stream we could just reference count all streams instead of treating one stream as the root marked by a boolean. Meaning we never call forceClose or track a boolean to identify the root stream. Other than the test case provided, how did you come across this issue in the wild? Is it the case of some abstraction returning a sub stream? |
As I understand, the document you mentioned is about that closing the root stream will close any derived streams created by It is a sensible behavior and I totally agree with that, and that's not what I am talking about. What I am pointing out in the issue (#694) and this PR is that G.C may close the root The solution I try to give is also about making the root I guess a way of the reference counting you suggested seems to solve the problem only after you remove its Actually, I also initially thought of the reference counting as a solution, but just made derived streams keep a reference to the root stream because removing or modifying |
I found it when I make a Let me give you a sample code to help you understand. Firstly, make a new public List<InputStream> extract() {
MimeMessage message = new MimeMessage(session, new SharedFileInputStream(getFile()));
List<InputStream> results = new ArrayList<>();
foreach (mimePart of message) {
if (...) {
InputStream extractedInputStream = mimePart.getDataHandler().getDataSource().getInputStream();
results.add(extractedInputStream);
}
}
return results;
} Note that mail-api/api/src/main/java/jakarta/mail/internet/MimeMessage.java Lines 335 to 356 in 29fc062
Secondly, invoke the public List<Integer> getSize() {
List<InputStream> extracted = extract();
return extracted.stream()
.map(is -> is.available())
.collect(Collectors.toList());
} I found It's because G.C invoked As a result, extracted I also checked |
I think the problem is this class is designed it such a way that it is a candidate for java.lang.ref.Reference.reachabilityFence I would think that we don't need to pin the root stream. It just needs to be kept alive long enough such that the counter gets incremented before the root is freed. E.G:
However, I would have thought synchronized keyword would be enough to keep it alive. Also I noticed that the close methods are not synchronized. This probably due to to multiple locks being acquired but I wonder if that is getting us into trouble since SharedFileInputStream::close is not synchronized it is just barging ahead of newStream. Synchronizing close might fix this issue with the root being G.C. too quickly. Given your test case it seems that we should move forward with fixing this issue. My preference would be to use the Cleaner API on the SharedFile and get rid of the finalize method on the SharedFileInputStream. However, I would like to see just the simple reference counting working before we move to Cleaner API. |
I did same tests (#695 (comment)) based on the sample code you gave. (Note that tests were run with original Firstly, added Secondly, added Third, added both. Test all failed. Actually, I don't understand what If there is something I can refer to, please let me know. so that I can add another tests.
This seems to be one of the reasonable ways to deal with this problem because it leaves file's lifecycle to the I also found that just removing I think if you fix the problem in that way, a completely new work needs to be done, closing this PR. It seems to be not that hard work. Do you want me to work on it? |
I think this because we also have to get rid of the concept of the root stream being able to close all streams and simply reference count. As in get rid of root/master boolean and forceClose methods. I could be wrong though.
In this case synchronized modifier means that the current thread has locked 'this' for the whole method call. Meaning that 'this' is reachable during the whole method call. Without synchronized modifier
This is explained at the bottom of:
Usually it is to ensure that a close stream is seen as closed to all threads.
Let me try to get consensus on the approach. The more I think about it the reason the finalizer exists on the SharedFileInputStream is to make the reference counting accurate. Which enables the last stream to close the native resource. Which is important if the last/root stream is strongly referenced. For sure is important on Windows due to open file locking. |
I see. All test passed after getting rid of master boolean and Please be aware of this result and refer to it for future works.
All right. Please let me know if there's any progress and anything that I can help. |
@SeongEon-Jo Apologizes for not getting back to you sooner on this PR. After looking this over and what has been learned from your previous code I think the approach should be:
I'm thinking with these changes this class should work as you expect it to and we won't need to pin the root stream. Thanks for all your efforts on this PR and helping me gain an understanding of what is going on with this class. |
All sounds good in your approach. All test passed fortunately after applying your suggestions.
You're welcome. I'm happy to contribute. 😎 Feel free again to let me know if you have more to add or edit. |
I am getting an error like below during build.
I surely added
What should I do to resolve this? |
@SeongEon-Jo Looks like we are targeting JDK8 still in the pom.xml. Instead of calling reachabilityFence You can do the same with:
|
@SeongEon-Jo Sorry for the delay on this. Codewise, I think this is all good. Just need to do a review of how it is used in the codebase before I merge it. I think you need to update the https://github.com/jakartaee/mail-api/blob/master/doc/release/CHANGES.txt
I think you also need to put a compatibility note in: https://github.com/jakartaee/mail-api/blob/master/www/docs/COMPAT.txt
I finally found in my notes the explanation on why requireNonNull works: https://mail.openjdk.org/pipermail/core-libs-dev/2018-February/051312.html Thanks for the help! Nice work! |
Updated exactly same way you suggested. Please let me know if there's anything which violates docs conventions 😂
Got this. Thanks for the kind explanation! |
@SeongEon-Jo I haven't forgotten about this! I'll do my best to get this reviewed and merged. Thank you for your patience. |
Committed as: |
resolves #694
Added reference to master
SharedFileInputStream
to slaves.By doing this, I guess it can retain reference to master
SharedFileInputStream
in order not to be junked by garbage collection until the reference to the last derived stream is gone.Feel free to comment if there are some points to add or fix.