-
Notifications
You must be signed in to change notification settings - Fork 1.2k
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
CODENVY-587: Handle closed consumers in composite line comsumer #2549
Conversation
@garagatyi @skabashnyuk review please |
Build success. https://ci.codenvycorp.com/job/che-pullrequests-build/478/ |
|
||
public CompositeLineConsumer(LineConsumer... lineConsumers) { | ||
this.lineConsumers = lineConsumers; | ||
this.lineConsumers = new ArrayList<>(lineConsumers.length); | ||
Arrays.stream(lineConsumers).forEach(this.lineConsumers::add); |
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.
is Arrays.asList(lineConsumers) not working ?
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.
@benoitf Arrays.asList
returns immutable list, which is not fit here
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.
why it's an issue to have immutable list ?
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.
because we have to remove closed consumers from the list
isClosed = true; | ||
} | ||
} catch (IOException e) { | ||
LOG.error(String.format("An error occurred while writing line to the line consumer %s", lineConsumer), e); |
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.
is LineConsumer always have nice toString() ? Could we say also the line that we tried to log ?
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.
@benoitf, we have default toString()
here, but I think that it is enough. We'll have consumer implementation class name and exception stacktrace.
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.
@benoitf, do you think we need to log that? For example, an output of user's command?
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.
maybe not I was just trying to see which error message could be helpful to debug/understand what was the issue
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.
@benoitf thank you, sometimes logged information isn't enough, but the problem here is the reason why it happens.
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.
agree with Florent - toString here has no sense
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.
I think it is still usable, because we will know consumer implementation
|
||
public CompositeLineConsumer(LineConsumer... lineConsumers) { | ||
this.lineConsumers = lineConsumers; | ||
this.lineConsumers = new ArrayList<>(lineConsumers.length); |
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.
Arrays.asList();
*/ | ||
public class CompositeLineConsumer implements LineConsumer { | ||
private static final Logger LOG = LoggerFactory.getLogger(CompositeLineConsumer.class); | ||
|
||
private final LineConsumer[] lineConsumers; | ||
private final List<LineConsumer> lineConsumers; | ||
private boolean isClosed; |
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.
Will it work in multi-threaded environment?
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.
thx, fixed
try { | ||
lineConsumer.writeLine(line); | ||
} catch (ConsumerAlreadyClosedException | ClosedByInterruptException e) { | ||
lineConsumers.remove(lineConsumer); // consumer is already closed, so we cannot write into it any 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.
Is it an appropriate way to remove from source while you are iterating through it?
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.
I would say a test is required with isClosed and writeLine throwing exceptions
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.
@mmorhun Please address my comment
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.
Fixed by using CopyOnWriteArrayList
import java.io.IOException; | ||
|
||
/** | ||
* @author Mykola Morhun |
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.
Javadocs
Signed-off-by: Mykola Morhun <[email protected]>
2bcbebc
to
54a5b81
Compare
Signed-off-by: Mykola Morhun <[email protected]>
Build success. https://ci.codenvycorp.com/job/che-pullrequests-build/535/ |
Signed-off-by: Mykola Morhun <[email protected]>
Build # 539 - FAILED Please check console output at https://ci.codenvycorp.com/job/che-pullrequests-build/539/ to view the results. |
} catch (IOException e) { | ||
LOG.error(String.format("An error occurred while closing the line consumer %s", lineConsumer), e); | ||
if (!isClosed) { | ||
isClosed = true; |
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.
These operations are not atomic. Consider usage of AtomicBoolean
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.
thx
try { | ||
lineConsumer.close(); | ||
} catch (IOException e) { | ||
LOG.error(String.format("An error occurred while closing the line consumer %s", lineConsumer), e); |
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.
I think that toString in line consumers returns pointer to memory area. Do we need that in error message?
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.
Yes, because it also returns name of implementation in which problem occurs
import java.util.Arrays; | ||
import java.util.Collections; | ||
import java.util.List; | ||
import java.util.concurrent.CopyOnWriteArrayList; | ||
|
||
/** | ||
* @author andrew00x |
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.
Please add javadocs
|
||
public CompositeLineConsumer(LineConsumer... lineConsumers) { | ||
this.lineConsumers = lineConsumers; | ||
this.lineConsumers = new CopyOnWriteArrayList<>(lineConsumers); | ||
this.isClosed = false; | ||
} | ||
|
||
@Override | ||
public void close() throws IOException { |
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.
Please add javadoc that explains behavior of this implementation of consumer for writeLine and close methods
try { | ||
lineConsumer.writeLine(line); | ||
} catch (ConsumerAlreadyClosedException | ClosedByInterruptException e) { | ||
lineConsumers.remove(lineConsumer); // consumer is already closed, so we cannot write into it any 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.
@mmorhun Please address my comment
for (LineConsumer lineConsumer : lineConsumers) { | ||
try { | ||
lineConsumer.writeLine(line); | ||
} catch (ConsumerAlreadyClosedException | ClosedByInterruptException e) { |
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.
If ClosedByInterruptException is thrown this class should not continue work
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.
Why? Only one subconsumer is closed. What is the reason to close others?
isClosed = true; | ||
} | ||
} catch (IOException e) { | ||
LOG.error(String.format("An error occurred while writing line to the line consumer %s", lineConsumer), e); |
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.
agree with Florent - toString here has no sense
@@ -19,7 +20,7 @@ | |||
* @author Alexander Garagatyi | |||
*/ | |||
public interface ProgressMonitor { | |||
void updateProgress(ProgressStatus currentProgressStatus); |
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.
Throwing this exception here doesn't make sense because this class doesn't declare any dependency on line consumer
@@ -34,11 +34,18 @@ public File getFile() { | |||
|
|||
@Override | |||
public void writeLine(String line) throws IOException { | |||
if (line != null) { | |||
writer.write(line); | |||
try { |
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.
please check closing status here
Build # 568 - FAILED Please check console output at https://ci.codenvycorp.com/job/che-pullrequests-build/568/ to view the results. |
Signed-off-by: Mykola Morhun <[email protected]>
Build # 612 - FAILED Please check console output at https://ci.codenvycorp.com/job/che-pullrequests-build/612/ to view the results. |
Build # 732 - FAILED Please check console output at https://ci.codenvycorp.com/job/che-pullrequests-build/732/ to view the results. |
Signed-off-by: Mykola Morhun <[email protected]>
*/ | ||
@Override | ||
public void close() { | ||
if (isOpen) { |
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.
Is this thread-safe?
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.
thx, I forgot volatile
*/ | ||
@Override | ||
public void writeLine(String line) { | ||
if (isOpen && lock.readLock().tryLock()) { |
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.
Is this thread-safe?
import java.util.concurrent.locks.ReentrantReadWriteLock; | ||
|
||
/** | ||
* @author andrew00x |
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.
Javadocs?
|
||
/** | ||
* This line consumer consists of several consumers and copies each consumed line into all subconsumers. | ||
* Is used when lines should be written into two or more consumers. |
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.
Add a note that it is not thread-safe
@@ -18,14 +20,20 @@ | |||
|
|||
/** | |||
* @author andrew00x |
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.
Javadocs? And add a note that it is not thread-safe
public void shouldBeAbleToWriteIntoFile() throws Exception { | ||
// given | ||
final String message = "Test line"; | ||
injectWriterMock(fileLineConsumer, writer); |
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.
Is it better to do that in single place?
concurrentCompositeLineConsumer.writeLine(message); | ||
|
||
// then | ||
for (LineConsumer subConsumer : subConsumers) { |
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.
The same comments to the whole class as to the non multi-threaded version.
verify(subConsumer, never()).writeLine(eq(message)); | ||
} | ||
} | ||
|
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.
I don't see any multi-threaded tests
writerField.set(concurrentFileLineConsumer, writerMock); | ||
} | ||
|
||
} |
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.
See comments above. I don't see multi-threaded tests
@@ -12,6 +12,8 @@ | |||
|
|||
import org.eclipse.che.plugin.docker.client.json.ProgressStatus; | |||
|
|||
import java.io.IOException; |
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.
Please clean up this class
Build # 860 - FAILED Please check console output at https://ci.codenvycorp.com/job/che-pullrequests-build/860/ to view the results. |
Signed-off-by: Mykola Morhun <[email protected]>
Build # 871 - FAILED Please check console output at https://ci.codenvycorp.com/job/che-pullrequests-build/871/ to view the results. |
Signed-off-by: Mykola Morhun <[email protected]>
Signed-off-by: Mykola Morhun <[email protected]>
Build success. https://ci.codenvycorp.com/job/che-pullrequests-build/885/ |
* Consumes logs and writes them into file. | ||
* <br/> | ||
* This class is not thread safe. | ||
* Also see multithreaded implementation {@link org.eclipse.che.api.core.util.lineconsumer.ConcurrentFileLineConsumer} |
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.
Links to other classes can be formatted with @see annotation
@@ -22,6 +22,9 @@ | |||
/** | |||
* This line consumer consists of several consumers and copies each consumed line into all subconsumers. | |||
* Is used when lines should be written into two or more consumers. | |||
* <br/> | |||
* This class is not thread safe. | |||
* Also see multithreaded implementation {@link org.eclipse.che.api.core.util.lineconsumer.ConcurrentCompositeLineConsumer} |
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.
Links to other classes can be formatted with @see annotation
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.
Nice job!
Please consider suggested improvements
verify(subConsumer, never()).writeLine(eq(message)); | ||
} | ||
} | ||
|
||
private LineConsumer[] appendTo(LineConsumer[] base, LineConsumer... toAppend ) { | ||
List<LineConsumer> allElements = new ArrayList<>(Arrays.asList(base)); |
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.
I think that you can add base element in the head of list after creation of a list from other elements instead
} | ||
|
||
private LineConsumer[] appendTo(LineConsumer[] base, LineConsumer... toAppend ) { | ||
List<LineConsumer> allElements = new ArrayList<>(Arrays.asList(base)); |
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.
See comment above
} | ||
|
||
@Test | ||
public void shouldIgnoreWriteToSubConsumersWhenLockForCloseIsLocked() throws Exception { |
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.
afterCloseWasCalled?
executor.execute(concurrentCompositeLineConsumer::close); | ||
|
||
waitingAnswer1.completeAnswer(); | ||
waitingAnswer2.completeAnswer(); |
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.
Please ensure that close methods weren't called before all writes are finished
|
||
for (LineConsumer consumer : subConsumers) { | ||
verify(consumer).writeLine(eq(message1)); | ||
verify(consumer).writeLine(eq(message2)); |
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.
what about verifying that close methods of internal consumers were called?
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.
ok
WaitingAnswer<Void> waitingAnswer = new WaitingAnswer<>(); | ||
doAnswer(waitingAnswer).when(writer).close(); | ||
|
||
executor.execute(this::closeConsumer); |
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.
I'm not sure that it is OK to ignore possible exception here
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.
An exception never be thrown here because Writer is a mock.
waitingAnswer.waitAnswerCall(1, TimeUnit.SECONDS); | ||
|
||
// when | ||
writeIntoConsumer("Test line"); |
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.
I'm not sure that it is OK to ignore possible exception here
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.
I think, that it is OK. IMO, good test shouldn't know about an implementation, but be able to verify correct work of functionality. Here we shouldn't care about implementation behaviour in the test case (do nothing, log warning/error or throw an exception). All what we need to know that method which prints message in subconsumer wasn't invoked.
concurrentFileLineConsumer = new ConcurrentFileLineConsumer(file); | ||
injectWriterMock(concurrentFileLineConsumer, writer); | ||
|
||
executor = Executors.newFixedThreadPool(5); |
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.
Do you really use 5 threadds here?
for (LineConsumer lineConsumer : subConsumers) { | ||
doNothing().when(lineConsumer).writeLine(anyString()); | ||
} | ||
executor = Executors.newFixedThreadPool(5); |
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.
Do you really use 5 threads here?
Signed-off-by: Mykola Morhun <[email protected]>
Build success. https://ci.codenvycorp.com/job/che-pullrequests-build/914/ |
Signed-off-by: Mykola Morhun <[email protected]>
Build # 921 - FAILED Please check console output at https://ci.codenvycorp.com/job/che-pullrequests-build/921/ to view the results. |
# Conflicts: # wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/environment/server/CheEnvironmentEngine.java
Build success. https://ci.codenvycorp.com/job/che-pullrequests-build/1007/ |
What does this PR do?
Fix wrong behaviour of Composite and File line consumer.
Add concurrent versions of Composite and File line consumers.
Add tests.
What issues does this PR fix or reference?
codenvy/codenvy#587
Previous behavior
Composite and File line consumers tried to write into all subconsumers
New behavior
Composite and File line consumers write only to non-closed subconsumers