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

Shareable executors #616

Merged
merged 16 commits into from
Mar 5, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
Copyright (C) 2013-2019 Expedia Inc.
Copyright (C) 2013-2020 Expedia Inc.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
Expand Down
21 changes: 14 additions & 7 deletions components/common/src/main/java/com/hotels/styx/NettyExecutor.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@

import static com.hotels.styx.EventLoopGroups.epollEventLoopGroup;
import static com.hotels.styx.EventLoopGroups.nioEventLoopGroup;
import static java.util.concurrent.TimeUnit.SECONDS;

/**
* A netty based executor for styx.
Expand All @@ -41,7 +42,8 @@ public class NettyExecutor {

/**
* Constructs an netty/io event executor.
* @param name thread group name.
*
* @param name thread group name.
* @param count thread count.
* @return
*/
Expand All @@ -62,15 +64,20 @@ public static NettyExecutor create(String name, int count) {
}

private NettyExecutor(EventLoopGroup eventLoopGroup,
Class<? extends ServerChannel> serverEventLoopClass,
Class<? extends SocketChannel> clientEventLoopClass) {
this.serverEventLoopClass = serverEventLoopClass;
this.clientEventLoopClass = clientEventLoopClass;
this.eventLoopGroup = eventLoopGroup;
Class<? extends ServerChannel> serverEventLoopClass,
Class<? extends SocketChannel> clientEventLoopClass) {
this.serverEventLoopClass = serverEventLoopClass;
this.clientEventLoopClass = clientEventLoopClass;
this.eventLoopGroup = eventLoopGroup;
}

public void shut() {
eventLoopGroup.shutdownGracefully();
try {
eventLoopGroup.shutdownGracefully(0, 0, SECONDS).await(5000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException(e);
Copy link
Contributor

Choose a reason for hiding this comment

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

This is clearing the interrupted flag, we've discussed previously it might not be the best option.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks. A good catch :-)

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.

}
}

public Class<? extends ServerChannel> serverEventLoopClass() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
Copyright (C) 2013-2020 Expedia Inc.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package com.hotels.styx;

import com.fasterxml.jackson.databind.JsonNode;

/**
* A generic factory that can be implemented to create executor objects whose type is not known
* until read from configuration.
*
*/
public interface ExecutorFactory {
/**
* Create an executor instance.
*
* @param name Executor name
* @param configuration Styx executor configuration
*
* @return Styx service instance
*/
NettyExecutor create(String name, JsonNode configuration);
Copy link
Contributor

Choose a reason for hiding this comment

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

Should we try not to not pass a JsonNode here? It seems we keep propagating our Yaml based config.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This code follows the idiomatic fashion of creating styx objects from yaml/json nodes. Sadly the styx object model relies entirely on yaml and/or JsonNode based configuration.

I have created a a proof of concept that lifts this restriction. Check the details in PR: #609 (Remove old application router).

Till then, it needs to stay in this "idiomatic" form.

}
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
import static com.hotels.styx.config.schema.SchemaDsl.string;
import static com.hotels.styx.config.schema.SchemaDsl.union;
import static com.hotels.styx.config.validator.DocumentFormat.newDocument;
import static com.hotels.styx.routing.config.Builtins.BUILTIN_EXECUTOR_SCHEMAS;
import static com.hotels.styx.routing.config.Builtins.BUILTIN_HANDLER_SCHEMAS;
import static com.hotels.styx.routing.config.Builtins.BUILTIN_SERVER_SCHEMAS;
import static com.hotels.styx.routing.config.Builtins.BUILTIN_SERVICE_PROVIDER_SCHEMAS;
Expand Down Expand Up @@ -111,6 +112,7 @@ final class ServerConfigSchema {
field("expirationMillis", integer())
))
)),
optional("executors", map(routingObject())),
optional("services", object(
field("factories", map(object(opaque())))
)),
Expand Down Expand Up @@ -166,6 +168,7 @@ final class ServerConfigSchema {
BUILTIN_SERVICE_PROVIDER_SCHEMAS.forEach(STYX_SERVER_CONFIGURATION_SCHEMA_BUILDER::typeExtension);
BUILTIN_SERVER_SCHEMAS.forEach(STYX_SERVER_CONFIGURATION_SCHEMA_BUILDER::typeExtension);
INTERCEPTOR_SCHEMAS.forEach(STYX_SERVER_CONFIGURATION_SCHEMA_BUILDER::typeExtension);
BUILTIN_EXECUTOR_SCHEMAS.forEach(STYX_SERVER_CONFIGURATION_SCHEMA_BUILDER::typeExtension);
}


Expand Down
25 changes: 20 additions & 5 deletions components/proxy/src/main/java/com/hotels/styx/StyxServer.java
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,8 @@ public final class StyxServer extends AbstractService {
private final ServiceManager phase2Services;
private final Stopwatch stopwatch;
private final StyxServerComponents components;
private NettyExecutor proxyBossExecutor;
private NettyExecutor proxyWorkerExecutor;

public static void main(String[] args) {
try {
Expand Down Expand Up @@ -192,14 +194,18 @@ public StyxServer(StyxServerComponents components, Stopwatch stopwatch) {

// Phase 2: start HTTP services;
StyxConfig styxConfig = components.environment().configuration();

proxyBossExecutor = NettyExecutor.create("Proxy-Boss", styxConfig.proxyServerConfig().bossThreadsCount());
proxyWorkerExecutor = NettyExecutor.create("Proxy-Worker", styxConfig.proxyServerConfig().workerThreadsCount());

httpServer = styxConfig.proxyServerConfig()
.httpConnectorConfig()
.map(it -> httpServer(components.environment(), it, handlerForOldProxyServer))
.map(it -> httpServer(components, it, handlerForOldProxyServer))
.orElse(null);

httpsServer = styxConfig.proxyServerConfig()
.httpsConnectorConfig()
.map(it -> httpServer(components.environment(), it, handlerForOldProxyServer))
.map(it -> httpServer(components, it, handlerForOldProxyServer))
.orElse(null);

ArrayList<Service> services2 = new ArrayList<>();
Expand Down Expand Up @@ -235,7 +241,8 @@ public InetSocketAddress adminHttpAddress() {
return adminServer.inetAddress();
}

private static InetServer httpServer(Environment environment, ConnectorConfig connectorConfig, HttpHandler styxDataPlane) {
private InetServer httpServer(StyxServerComponents components, ConnectorConfig connectorConfig, HttpHandler styxDataPlane) {
Environment environment = components.environment();
CharSequence styxInfoHeaderName = environment.configuration().styxHeaderConfig().styxInfoHeaderName();
ResponseInfoFormat responseInfoFormat = new ResponseInfoFormat(environment);

Expand All @@ -251,8 +258,8 @@ private static InetServer httpServer(Environment environment, ConnectorConfig co

return NettyServerBuilder.newBuilder()
.setMetricsRegistry(environment.metricRegistry())
.bossExecutor(NettyExecutor.create("Proxy-Boss", environment.configuration().proxyServerConfig().bossThreadsCount()))
.workerExecutor(NettyExecutor.create("Proxy-Worker", environment.configuration().proxyServerConfig().workerThreadsCount()))
.bossExecutor(proxyBossExecutor)
.workerExecutor(proxyWorkerExecutor)
.setProtocolConnector(proxyConnector)
.handler(styxDataPlane)
.build();
Expand Down Expand Up @@ -304,6 +311,14 @@ protected void doStart() {
@Override
protected void doStop() {
this.phase2Services.stopAsync().awaitStopped();

proxyBossExecutor.shut();
proxyWorkerExecutor.shut();

this.components.executors()
.entrySet()
.forEach(entry -> entry.getValue().component4().shut());

this.phase1Services.stopAsync().awaitStopped();
shutdownLogging(true);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,12 +122,18 @@ public InetServer build() {
StyxConfig styxConfig = environment.configuration();
AdminServerConfig adminServerConfig = styxConfig.adminServerConfig();

NettyExecutor executor = NettyExecutor.create("Admin-Boss", adminServerConfig.bossThreadsCount());
NettyExecutor bossExecutor = NettyExecutor.create("Admin-Boss", adminServerConfig.bossThreadsCount());
NettyExecutor workerExecutor = NettyExecutor.create("Admin-Worker", adminServerConfig.workerThreadsCount());

NettyServerBuilder builder = NettyServerBuilder.newBuilder()
.setMetricsRegistry(environment.metricRegistry())
.bossExecutor(executor)
.workerExecutor(NettyExecutor.create("Admin-Worker", adminServerConfig.workerThreadsCount()))
.handler(adminEndpoints(styxConfig, startupConfig));
.bossExecutor(bossExecutor)
.workerExecutor(workerExecutor)
.handler(adminEndpoints(styxConfig, startupConfig))
.shutdownAction(() -> {
bossExecutor.shut();
workerExecutor.shut();
});

// Currently admin server cannot be started over TLS protocol.
// This appears to be an existing issue that needs rectifying.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ public List<String> placeholderNames() {
private static Pattern compilePattern(String pattern) {
Matcher matcher = PLACEHOLDER_PATTERN.matcher(pattern);

return Pattern.compile(matcher.replaceAll("(?<$1>[a-zA-Z0-9-_]+)"));
return Pattern.compile(matcher.replaceAll("(?<$1>[a-zA-Z0-9-_.]+)"));
}

private static List<String> placeholders(String pattern) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,14 @@
package com.hotels.styx.routing.config;

import com.google.common.collect.ImmutableMap;
import com.hotels.styx.ExecutorFactory;
import com.hotels.styx.InetServer;
import com.hotels.styx.NettyExecutor;
import com.hotels.styx.api.Eventual;
import com.hotels.styx.api.HttpInterceptor;
import com.hotels.styx.api.extension.service.spi.StyxService;
import com.hotels.styx.config.schema.Schema;
import com.hotels.styx.executors.NettyExecutorFactory;
import com.hotels.styx.routing.RoutingObject;
import com.hotels.styx.routing.db.StyxObjectStore;
import com.hotels.styx.routing.handlers.ConditionRouter;
Expand Down Expand Up @@ -94,6 +97,14 @@ YAML_FILE_CONFIGURATION_SERVICE, new YamlFileConfigurationServiceFactory()
"HttpServer", StyxHttpServer.SCHEMA
);

public static final ImmutableMap<String, ExecutorFactory> BUILTIN_EXECUTOR_FACTORIES = ImmutableMap.of(
"NettyExecutor", new NettyExecutorFactory()
);

public static final ImmutableMap<String, Schema.FieldType> BUILTIN_EXECUTOR_SCHEMAS = ImmutableMap.of(
"NettyExecutor", NettyExecutorFactory.SCHEMA
);

public static final RouteRefLookup DEFAULT_REFERENCE_LOOKUP = reference -> (request, ctx) ->
Eventual.of(response(NOT_FOUND)
.body(format("Handler not found for '%s'.", reference), UTF_8)
Expand Down Expand Up @@ -220,4 +231,24 @@ public static InetServer buildServer(

return constructor.create(name, context, serverDef.config(), serverDb);
}

/**
* Builds a Styx executor object.
*
* Styx server is a service that can accept incoming traffic from the client hosts.
*
* @param name Styx service name
* @param serverDef Styx service object configuration
* @param factories Service provider factories by name
*
* @return a Styx service
*/
public static NettyExecutor buildExecutor(
String name,
StyxObjectDefinition serverDef,
Map<String, ExecutorFactory> factories) {
ExecutorFactory constructor = factories.get(serverDef.type());
checkArgument(constructor != null, format("Unknown executor type '%s' for '%s' provider", serverDef.type(), serverDef.name()));
return constructor.create(name, serverDef.config());
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
Copyright (C) 2013-2019 Expedia Inc.
Copyright (C) 2013-2020 Expedia Inc.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
Expand All @@ -16,6 +16,9 @@
package com.hotels.styx.routing.config;

import com.hotels.styx.Environment;
import com.hotels.styx.NettyExecutor;
import com.hotels.styx.StyxObjectRecord;
import com.hotels.styx.api.configuration.ObjectStore;
import com.hotels.styx.proxy.plugin.NamedPlugin;
import com.hotels.styx.routing.RoutingObject;
import com.hotels.styx.routing.RoutingObjectRecord;
Expand Down Expand Up @@ -61,23 +64,28 @@ class Context {
private final Iterable<NamedPlugin> plugins;
private final Map<String, HttpInterceptorFactory> interceptorFactories;
private final boolean requestTracking;
private StyxObjectStore<StyxObjectRecord<NettyExecutor>> executorObjectStore;

// CHECKSTYLE:OFF
public Context(
RouteRefLookup refLookup,
Environment environment,
StyxObjectStore<RoutingObjectRecord> routeDb,
Map<String, RoutingObjectFactory> objectFactories,
Iterable<NamedPlugin> plugins,
Map<String, HttpInterceptorFactory> interceptorFactories,
boolean requestTracking) {
boolean requestTracking,
StyxObjectStore<StyxObjectRecord<NettyExecutor>> executorObjectStore) {
this.refLookup = refLookup;
this.environment = requireNonNull(environment);
this.routeDb = requireNonNull(routeDb);
this.objectFactories = requireNonNull(objectFactories);
this.plugins = requireNonNull(plugins);
this.interceptorFactories = requireNonNull(interceptorFactories);
this.requestTracking = requestTracking;
this.executorObjectStore = executorObjectStore;
}
// CHECKSTYLE:ON

public Environment environment() {
return environment;
Expand Down Expand Up @@ -106,5 +114,9 @@ public boolean requestTracking() {
public RouteRefLookup refLookup() {
return refLookup;
}

public ObjectStore<StyxObjectRecord<NettyExecutor>> executors() {
return executorObjectStore;
};
}
}
Loading