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

CODENVY-587: Handle closed consumers in composite line comsumer #2549

Merged
merged 16 commits into from
Nov 15, 2016

Conversation

mmorhun
Copy link
Contributor

@mmorhun mmorhun commented Sep 22, 2016

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

@mmorhun
Copy link
Contributor Author

mmorhun commented Sep 22, 2016

@garagatyi @skabashnyuk review please

@codenvy-ci
Copy link


public CompositeLineConsumer(LineConsumer... lineConsumers) {
this.lineConsumers = lineConsumers;
this.lineConsumers = new ArrayList<>(lineConsumers.length);
Arrays.stream(lineConsumers).forEach(this.lineConsumers::add);
Copy link
Contributor

@benoitf benoitf Sep 23, 2016

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 ?

Copy link
Contributor Author

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

Copy link
Contributor

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 ?

Copy link
Contributor Author

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);
Copy link
Contributor

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 ?

Copy link
Contributor Author

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.

Copy link
Contributor Author

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?

Copy link
Contributor

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

Copy link
Contributor Author

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.

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

Copy link
Contributor Author

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);

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;

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?

Copy link
Contributor Author

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

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?

Copy link
Contributor

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

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

Copy link
Contributor Author

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

Choose a reason for hiding this comment

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

Javadocs

@codenvy-ci
Copy link

@codenvy-ci
Copy link

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;

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

Copy link
Contributor Author

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);

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?

Copy link
Contributor Author

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

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 {

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

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) {

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

Copy link
Contributor Author

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);

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);

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 {

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

@codenvy-ci
Copy link

Build # 568 - FAILED

Please check console output at https://ci.codenvycorp.com/job/che-pullrequests-build/568/ to view the results.

@codenvy-ci
Copy link

Build # 612 - FAILED

Please check console output at https://ci.codenvycorp.com/job/che-pullrequests-build/612/ to view the results.

@codenvy-ci
Copy link

Build # 732 - FAILED

Please check console output at https://ci.codenvycorp.com/job/che-pullrequests-build/732/ to view the results.

*/
@Override
public void close() {
if (isOpen) {

Choose a reason for hiding this comment

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

Is this thread-safe?

Copy link
Contributor Author

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()) {

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

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.

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

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);

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) {

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));
}
}

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);
}

}

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;

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

@codenvy-ci
Copy link

Build # 860 - FAILED

Please check console output at https://ci.codenvycorp.com/job/che-pullrequests-build/860/ to view the results.

@codenvy-ci
Copy link

Build # 871 - FAILED

Please check console output at https://ci.codenvycorp.com/job/che-pullrequests-build/871/ to view the results.

Mykola Morhun added 3 commits November 1, 2016 18:13
@codenvy-ci
Copy link

* 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}

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}

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

Copy link

@garagatyi garagatyi left a 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));

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));

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 {

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();

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));

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?

Copy link
Contributor Author

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);

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

Copy link
Contributor Author

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");

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

Copy link
Contributor Author

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);

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);

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]>
@codenvy-ci
Copy link

@codenvy-ci
Copy link

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
@codenvy-ci
Copy link

@mmorhun mmorhun added this to the 5.0.0-M8 milestone Nov 15, 2016
@mmorhun mmorhun merged commit 7f2875e into master Nov 15, 2016
@mmorhun mmorhun deleted the CODENVY-587 branch November 15, 2016 13:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants