-
Notifications
You must be signed in to change notification settings - Fork 17
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
Use a separate ExecutorService to fix #71. #72
base: master
Are you sure you want to change the base?
Changes from all 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 |
---|---|---|
@@ -1,11 +1,11 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
|
||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | ||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> | ||
<modelVersion>4.0.0</modelVersion> | ||
|
||
<groupId>com.monitorjbl.plugins</groupId> | ||
<artifactId>pr-harmony</artifactId> | ||
<version>2.5.0</version> | ||
<version>2.5.1-SNAPSHOT</version> | ||
<packaging>atlassian-plugin</packaging> | ||
|
||
<name>PR Harmony</name> | ||
|
@@ -33,64 +33,79 @@ | |
<groupId>com.atlassian.bitbucket.server</groupId> | ||
<artifactId>bitbucket-api</artifactId> | ||
<scope>provided</scope> | ||
<version>4.11.1</version> | ||
</dependency> | ||
<dependency> | ||
<groupId>com.atlassian.bitbucket.server</groupId> | ||
<artifactId>bitbucket-spi</artifactId> | ||
<artifactId>bitbucket-build-api</artifactId> | ||
<scope>provided</scope> | ||
</dependency> | ||
<dependency> | ||
<groupId>com.atlassian.sal</groupId> | ||
<artifactId>sal-api</artifactId> | ||
<groupId>com.atlassian.bitbucket.server</groupId> | ||
<artifactId>bitbucket-spi</artifactId> | ||
<scope>provided</scope> | ||
</dependency> | ||
<dependency> | ||
<groupId>com.atlassian.bitbucket.server</groupId> | ||
<artifactId>bitbucket-build-api</artifactId> | ||
<artifactId>bitbucket-util</artifactId> | ||
<scope>provided</scope> | ||
</dependency> | ||
|
||
<dependency> | ||
<groupId>com.atlassian.plugins.rest</groupId> | ||
<artifactId>atlassian-rest-common</artifactId> | ||
<scope>provided</scope> | ||
</dependency> | ||
<dependency> | ||
<groupId>com.atlassian.sal</groupId> | ||
<artifactId>sal-api</artifactId> | ||
<scope>provided</scope> | ||
</dependency> | ||
<dependency> | ||
<groupId>com.atlassian.templaterenderer</groupId> | ||
<artifactId>atlassian-template-renderer-api</artifactId> | ||
<version>1.5.7</version> | ||
<scope>provided</scope> | ||
</dependency> | ||
|
||
<dependency> | ||
<groupId>javax.servlet</groupId> | ||
<artifactId>javax.servlet-api</artifactId> | ||
<scope>provided</scope> | ||
</dependency> | ||
<dependency> | ||
<groupId>commons-lang</groupId> | ||
<artifactId>commons-lang</artifactId> | ||
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. This was unused |
||
<version>2.6</version> | ||
<scope>provided</scope> | ||
</dependency> | ||
<dependency> | ||
<groupId>com.google.guava</groupId> | ||
<artifactId>guava</artifactId> | ||
<version>21.0</version> | ||
<scope>provided</scope> | ||
</dependency> | ||
|
||
<!-- Base test dependencies --> | ||
<dependency> | ||
<groupId>com.atlassian.bitbucket.server</groupId> | ||
<artifactId>bitbucket-it-common</artifactId> | ||
<scope>test</scope> | ||
</dependency> | ||
<dependency> | ||
<groupId>com.atlassian.bitbucket.server</groupId> | ||
<artifactId>bitbucket-page-objects</artifactId> | ||
<scope>test</scope> | ||
</dependency> | ||
<dependency> | ||
<groupId>ch.qos.logback</groupId> | ||
<artifactId>logback-classic</artifactId> | ||
<scope>test</scope> | ||
</dependency> | ||
<dependency> | ||
<groupId>junit</groupId> | ||
<artifactId>junit</artifactId> | ||
<version>4.11</version> | ||
<scope>test</scope> | ||
</dependency> | ||
<dependency> | ||
<groupId>org.hamcrest</groupId> | ||
<artifactId>hamcrest-all</artifactId> | ||
<version>1.3</version> | ||
<artifactId>hamcrest-library</artifactId> | ||
<scope>test</scope> | ||
</dependency> | ||
<dependency> | ||
<groupId>org.mockito</groupId> | ||
<artifactId>mockito-core</artifactId> | ||
<version>1.9.5</version> | ||
<exclusions> | ||
<exclusion> | ||
<groupId>org.hamcrest</groupId> | ||
|
@@ -99,47 +114,9 @@ | |
</exclusions> | ||
<scope>test</scope> | ||
</dependency> | ||
|
||
<!-- WIRED TEST RUNNER DEPENDENCIES --> | ||
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. I've replaced all of these dependencies with new dependencies on Bitbucket Server's common test modules ( |
||
<dependency> | ||
<groupId>com.atlassian.plugins</groupId> | ||
<artifactId>atlassian-plugins-osgi-testrunner</artifactId> | ||
<version>${plugin.testrunner.version}</version> | ||
<scope>test</scope> | ||
</dependency> | ||
<dependency> | ||
<groupId>javax.ws.rs</groupId> | ||
<artifactId>jsr311-api</artifactId> | ||
<version>1.1.1</version> | ||
<scope>provided</scope> | ||
</dependency> | ||
<dependency> | ||
<groupId>com.google.code.gson</groupId> | ||
<artifactId>gson</artifactId> | ||
<version>2.2.2-atlassian-1</version> | ||
</dependency> | ||
<dependency> | ||
<groupId>org.slf4j</groupId> | ||
<artifactId>slf4j-log4j12</artifactId> | ||
<version>1.7.12</version> | ||
<scope>test</scope> | ||
</dependency> | ||
<dependency> | ||
<groupId>org.seleniumhq.selenium</groupId> | ||
<artifactId>selenium-firefox-driver</artifactId> | ||
<version>2.53.1</version> | ||
<scope>test</scope> | ||
</dependency> | ||
<dependency> | ||
<groupId>commons-logging</groupId> | ||
<artifactId>commons-logging</artifactId> | ||
<version>1.2</version> | ||
<scope>test</scope> | ||
</dependency> | ||
</dependencies> | ||
|
||
<build> | ||
<testSourceDirectory>${project.basedir}/src/test/java/ut</testSourceDirectory> | ||
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. Rather than separate the source like this, I've switch the setup to have Surefire (used to run unit tests) ignore everything under (Feel free to not do it this way; this is just how we write our own plugins.) |
||
<plugins> | ||
<plugin> | ||
<groupId>com.atlassian.maven.plugins</groupId> | ||
|
@@ -151,64 +128,55 @@ | |
<product> | ||
<id>bitbucket</id> | ||
<instanceId>bitbucket</instanceId> | ||
<version>${bitbucket.version}</version> | ||
<dataVersion>${bitbucket.data.version}</dataVersion> | ||
<version>${bitbucket.test.version}</version> | ||
<dataVersion>${bitbucket.test.version}</dataVersion> | ||
</product> | ||
</products> | ||
<testGroups> | ||
<testGroup> | ||
<id>bitbucket-integration</id> | ||
<includes> | ||
<include>it/**/*.java</include> | ||
</includes> | ||
<productIds> | ||
<productId>bitbucket</productId> | ||
</productIds> | ||
</testGroup> | ||
</testGroups> | ||
</configuration> | ||
</plugin> | ||
<plugin> | ||
<groupId>org.apache.maven.plugins</groupId> | ||
<artifactId>maven-compiler-plugin</artifactId> | ||
<version>3.3</version> | ||
<version>3.7.0</version> | ||
<configuration> | ||
<source>1.8</source> | ||
<target>1.8</target> | ||
</configuration> | ||
</plugin> | ||
<plugin> | ||
<groupId>org.codehaus.mojo</groupId> | ||
<artifactId>build-helper-maven-plugin</artifactId> | ||
<version>3.0.0</version> | ||
<groupId>org.apache.maven.plugins</groupId> | ||
<artifactId>maven-surefire-plugin</artifactId> | ||
<version>2.20.1</version> | ||
<configuration> | ||
<excludes> | ||
<exclude>it/**/*.java</exclude> | ||
</excludes> | ||
</configuration> | ||
</plugin> | ||
</plugins> | ||
</build> | ||
|
||
<profiles> | ||
<profile> | ||
<id>integration</id> | ||
<activation> | ||
<activeByDefault>false</activeByDefault> | ||
</activation> | ||
<build> | ||
<plugins> | ||
<plugin> | ||
<groupId>org.codehaus.mojo</groupId> | ||
<artifactId>build-helper-maven-plugin</artifactId> | ||
<executions> | ||
<execution> | ||
<phase>generate-sources</phase> | ||
<goals> | ||
<goal>add-test-source</goal> | ||
</goals> | ||
<configuration> | ||
<sources> | ||
<source>${project.basedir}/src/test/java/it</source> | ||
</sources> | ||
</configuration> | ||
</execution> | ||
</executions> | ||
</plugin> | ||
</plugins> | ||
</build> | ||
</profile> | ||
</profiles> | ||
|
||
<properties> | ||
<bitbucket.version>5.0.1</bitbucket.version> | ||
<bitbucket.data.version>5.0.1</bitbucket.data.version> | ||
<amps.version>6.3.0</amps.version> | ||
<bitbucket.version>4.8.6</bitbucket.version> | ||
<bitbucket.data.version>${bitbucket.version}</bitbucket.data.version> | ||
<!-- This separate version allows testing the plugin with a different version of the system than | ||
it's compiled against, which is useful for testing compatibility with multiple versions --> | ||
<bitbucket.test.version>${bitbucket.version}</bitbucket.test.version> | ||
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. I'd introduced this separate version to allow, for example, compiling PR Harmony against Bitbucket Server 4.8 (which is the lower bound of the compatibility range you've set on Marketplace), but running the tests against Bitbucket Server 5.8 (the upper bound). You can run the integration tests against a given version with 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. Nice! |
||
<amps.version>6.3.14</amps.version> | ||
<plugin.testrunner.version>1.2.3</plugin.testrunner.version> | ||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> | ||
<!-- Skip executing integration tests by default. They can be run by adding -DskipITs=false when running Maven --> | ||
<skipITs>true</skipITs> | ||
</properties> | ||
</project> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,32 +1,48 @@ | ||
package com.monitorjbl.plugins; | ||
|
||
import com.atlassian.bitbucket.concurrent.BucketProcessor; | ||
import com.atlassian.bitbucket.util.Version; | ||
import com.atlassian.bitbucket.util.concurrent.ExecutorUtils; | ||
import com.atlassian.sal.api.ApplicationProperties; | ||
import com.atlassian.sal.api.lifecycle.LifecycleAware; | ||
import com.atlassian.util.concurrent.ThreadFactories; | ||
import org.slf4j.LoggerFactory; | ||
|
||
import javax.annotation.Nonnull; | ||
import java.io.Serializable; | ||
import java.util.List; | ||
import java.util.concurrent.ExecutorService; | ||
import java.util.concurrent.Executors; | ||
|
||
public class AsyncProcessor { | ||
private static final String PREFIX = "com.monitorjbl.plugins:pr-harmony:"; | ||
private final ExecutorService concurrencyService; | ||
public class AsyncProcessor implements LifecycleAware { | ||
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.
|
||
|
||
public AsyncProcessor(ExecutorService concurrencyService) { | ||
this.concurrencyService = concurrencyService; | ||
} | ||
private static final Version VERSION_5_9 = new Version(5, 9); | ||
|
||
@SuppressWarnings("unchecked") | ||
public <T extends Serializable> void dispatch(Runnable taskRequest) { | ||
concurrencyService.submit(taskRequest); | ||
} | ||
private final ExecutorService executorService; | ||
private final boolean ownsExecutor; | ||
|
||
public static abstract class TaskProcessor<T extends Serializable> implements BucketProcessor<T> { | ||
@Override | ||
public void process(@Nonnull String s, @Nonnull List<T> list) { | ||
list.forEach(this::handleTask); | ||
public AsyncProcessor(ApplicationProperties applicationProperties, ExecutorService executorService) { | ||
ownsExecutor = new Version(applicationProperties.getVersion()).compareTo(VERSION_5_9) < 0; | ||
if (ownsExecutor) { | ||
//For Bitbucket Server versions before 5.9, use our own ExecutorService to avoid starving out hook callbacks | ||
this.executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors(), | ||
ThreadFactories.namedThreadFactory("pr-harmony", ThreadFactories.Type.DAEMON)); | ||
} else { | ||
//Bitbucket Server 5.9 includes a fix for BSERV-10652, which moves hook callback to their own threadpool, so | ||
//for 5.9+ use the shared executor | ||
this.executorService = executorService; | ||
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. For 5.9, this allows PR Harmony to continue using our shared 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. Will this not cause thread exhaustion when the server gets a lot of build updates? If the executor wasn't shared, it wouldn't directly interfere with that (although it certainly could interfere with CPU/memory consumption). 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. This is a (difficult) balancing act. Certainly, having a significant number of build status changes could result in PR Harmony creating a lot of tasks for the shared executor. In a system where there are many add-ons using the shared executor, those build status tasks may delay tasks created by other add-ons--just like a large backlog of tasks from other add-ons would be able to delay PR Harmony's build status processing. For Bitbucket Server, though, that's actually desirable, as long as the tasks being delayed are not critical. That's the real "bug" in BSERV-10652; we were using the shared executor for critical processing: Hook callbacks. Having a shared limit on background processing that can be delayed helps to ensure the accumulation of add-ons doesn't overwhelm the system. (That's not to say that PR Harmony's build status processing is not important; clearly it is. But a delay of minutes is less critical than with, say, hosting operations.) Now, it could certainly be contended that if PR Harmony just always used its own executor, it wouldn't starve critical processing, but it also wouldn't starve, or be starved by, other add-ons. From a purely PR Harmony standpoint, that's all good. But from my standpoint, which I'll readily admit is biased, it's less good. The build status processing PR Harmony does is quite heavy, and produces non-trivial system load. When load becomes too high, it's usually not add-on vendors that see the support load, at least initially; it's Bitbucket Server's developers and supporters. If all add-ons create and use their own executors, it's likely some (most? all?) of them will use non-configurable sizes (as I've done here, I'll note, for 5.8 and earlier). But even if they are configurable, that still means a system administrator has to find out what all those configuration properties are and apply them--and they'd need to have some way of readily determining which add-on(s) executor(s) they need to reconfigure based on who's starving (or who's creating too much load). That's not always straightforward to figure out, especially for non-programmers. By comparison, if add-ons use the shared executor, it's a single thing to size. That implies, if tasks are getting backed up, administrators can consider their current system load and opt to add more threads to the shared executor, which would then be "spread" across all the add-ons that use it and allow the backlog to be cleared more quickly. Additionally, we're planning to add monitoring for the queue size on the shared executor in a future release of Bitbucket Server, which will make it easier for administrators to track the size of the backlog on their server. That's another feature add-ons which use their own executors won't benefit from. We may add an SPI that they could use (not sure what the plan is there, at the moment), but they'd still have to manually build it out. |
||
} | ||
} | ||
|
||
public abstract void handleTask(T task); | ||
public void dispatch(Runnable taskRequest) { | ||
executorService.execute(taskRequest); | ||
} | ||
|
||
@Override | ||
public void onStart() { | ||
//No-op | ||
} | ||
|
||
@Override | ||
public void onStop() { | ||
if (ownsExecutor) { | ||
ExecutorUtils.shutdown(executorService, LoggerFactory.getLogger(getClass())); | ||
} | ||
} | ||
} |
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.
In general, if you have a
<scope>import</scope>
forbitbucket-parent
, you shouldn't define explicit versions on your dependencies. Our parent POM defines the versions of the libraries that we actually ship with, so using those versions ensures you're (for example) using the "right" APIs for Bitbucket Server versions you target.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.
Thanks for the tip. The POM was largely copied from Atlassian's example plugin project, so if you haven't already you might want to update that to prevent others from doing this.