-
Notifications
You must be signed in to change notification settings - Fork 62
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
fix: JENKINS-44568, JENKINS-44414 #68
Changes from 2 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -33,12 +33,12 @@ | |
import java.util.Set; | ||
import java.util.concurrent.CopyOnWriteArraySet; | ||
|
||
import com.jcraft.jsch.ChannelExec; | ||
import com.jcraft.jsch.JSchException; | ||
import org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement; | ||
import org.slf4j.Logger; | ||
import org.slf4j.LoggerFactory; | ||
|
||
import com.jcraft.jsch.ChannelExec; | ||
import com.jcraft.jsch.JSchException; | ||
import com.sonymobile.tools.gerrit.gerritevents.dto.attr.Provider; | ||
import com.sonymobile.tools.gerrit.gerritevents.ssh.Authentication; | ||
import com.sonymobile.tools.gerrit.gerritevents.ssh.AuthenticationUpdater; | ||
|
@@ -94,6 +94,7 @@ public class GerritConnection extends Thread implements Connector { | |
private AuthenticationUpdater authenticationUpdater = null; | ||
private final Set<ConnectionListener> listeners = new CopyOnWriteArraySet<ConnectionListener>(); | ||
private int sshRxBufferSize = SSH_RX_BUFFER_SIZE; | ||
private StringBuilder eventBuffer = null; | ||
|
||
/** | ||
* Creates a GerritHandler with all the default values set. | ||
|
@@ -336,18 +337,39 @@ private void nullifyWatchdog() { | |
private String getLine(CharBuffer cb) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe makes sense to check if the message fits the buffer and just call StringBuilder if it does not. Otherwise these is a kind of dark magic in the code, which seems to hammer performance with no benefit |
||
String line = null; | ||
int pos = cb.position(); | ||
int limit = cb.limit(); | ||
cb.flip(); | ||
for (int i = 0; i < cb.length(); i++) { | ||
if (cb.charAt(i) == '\n') { | ||
line = getSubSequence(cb, 0, i).toString().trim(); | ||
line = getSubSequence(cb, 0, i).toString(); | ||
cb.position(i + 1); | ||
break; | ||
} | ||
} | ||
if (line != null) { | ||
cb.compact(); | ||
if (eventBuffer != null) { | ||
eventBuffer.append(line); | ||
String eventString = eventBuffer.toString(); | ||
eventBuffer = null; | ||
line = eventString; | ||
} | ||
line.trim(); | ||
} else { | ||
cb.clear().position(pos); | ||
if (cb.length() > 0) { | ||
if (cb.length() == cb.capacity()) { | ||
if (eventBuffer == null) { | ||
logger.debug("Encountered big event."); | ||
eventBuffer = new StringBuilder(); | ||
} | ||
eventBuffer.append(getSubSequence(cb, 0, pos)); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It is not effective to use String Builder in such way. It would be better to push the entire chunk once you hit the limit There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. But this is only called when the buffer is full There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. right, then it's not a problem. Misread the code |
||
} else { | ||
cb.position(pos); | ||
cb.limit(limit); | ||
} | ||
} else { | ||
cb.clear(); | ||
} | ||
} | ||
return line; | ||
} | ||
|
@@ -409,7 +431,9 @@ public void run() { | |
Integer readCount; | ||
while ((readCount = reader.read(cb)) != -1) { | ||
logger.debug("Read count from Gerrit stream: {}", String.valueOf(readCount)); | ||
int linecount = 0; | ||
while ((line = getLine(cb)) != null) { | ||
linecount++; | ||
logger.debug("Data-line from Gerrit: {}", line); | ||
if (handler != null) { | ||
handler.post(line, provider); | ||
|
@@ -424,7 +448,9 @@ public void run() { | |
if (!channel.isConnected() || !sshConnection.isConnected()) { | ||
throw new IllegalStateException("SSH connection is already lost."); | ||
} | ||
sleep(SSH_RX_SLEEP_MILLIS); | ||
if (linecount > 0) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not sure if this a good idea. The thing is if we get an event that is 10 times the size of the buffer, it would take 1 sec to read this event in. On the other hand if for some reason the reader.read is not blocking in case no data is there or it can't fill the buffer (like in the bug we try to fix) we would consume a lot of CPU time in this thread. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. have you tried it in your test environment to see if there is any real performance impact? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. could There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Probably I'm a bit too paranoid about this performance bottleneck. I encountered it while running the unit tests with a buffer size of 12, when suddently the tests started failing but output was correct. It just took too long to read everything while the unit tests started validating the calls. Maybe use |
||
sleep(SSH_RX_SLEEP_MILLIS); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It should fix JENKINS-44414 from what I see |
||
} | ||
} | ||
} catch (IOException ex) { | ||
logger.error("Stream events command error. ", ex); | ||
|
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 suggest to initialize it directly in the constructor so simplify the method logic.
StringBuilder is not thread safe, but it's perfectly fine for this case
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.
Really???
Thread implements Runnable. So the instance of GerritConnection can be used by multiple threads. (Though nobody want to use GerritConnection instance with such way)
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.
Actually when you have configured your Jenkins against 2 Gerrit instances you have 2 GerritConnection threads running.
But for the StringBuilder this is not a problem. It is an instance variable and the only place where it is created, modified or deleted is in the private getLine method. So no problem with multiple threads.
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 if such cycle is completed within one method call. But created instance is stored into a GerritConnection instance field then return a method call. Unfortunately it is not guaranteed that getLine method call is done only by the same thread during keeping this StringBuilder instance. My sample code might reproduce such situation.
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 yes, it would be better to guarantee it somehow or synchronize the method. You could try using the thread-safe
StringBuffer
, but in the current use-case it may become a performance killer. SynchronizedgetLine()
would be much better in this caseThere 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.
@mawinter69
Well, it's not @rinrinne says:
I understand that it may be used by non-GerritConnection threads.
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 this class intended to be used the way @rinrinne suggests, even though it could potentially be used in that way. And I don't think it is used in that way either. So documenting that it is only intended to be used by itself should suffice in that case if we think that the alternative will suffer big performance penalties.
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.
but getLine is a private method. How should a non GerritConnection thread access it? With reflection you can achieve this but why should one do 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.
Please run this code then check displayed object hash. It is generated within private method then stored into instance field.
https://gist.github.com/rinrinne/7e40638fba102d52b1d34a7b70b16b2d
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, true but as @rsandell pointed out, using the GerritConnection in this way would not only cause problems with the StringBuilder but also with the sshConnection, the Watchdog and so on.
The normal way would be