Skip to content

Commit

Permalink
#28593 testing multithreaded global Push approach
Browse files Browse the repository at this point in the history
  • Loading branch information
fabrizzio-dotCMS committed May 31, 2024
1 parent f28fc49 commit faf630d
Show file tree
Hide file tree
Showing 12 changed files with 314 additions and 67 deletions.
5 changes: 4 additions & 1 deletion tools/dotcms-cli/cli/docs/content-type-push.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ content-type-push - *Push content types*
// tag::picocli-generated-man-section-synopsis[]
== Synopsis

*content-type push* [*-h*] [*-dau*] [*--dry-run*] [*-ff*] [*-rct*]
*content-type push* [*-h*] [*-dau*] [*--dry-run*] [*-ff*] [*-rct*] [*-w*[=_watch_]]
[*--dotcms-url*=_<remoteURL>_] [*--retry-attempts*=_<retryAttempts>_]
[*-tk*=_<token>_] [_path_]

Expand Down Expand Up @@ -60,6 +60,9 @@ This command enables the pushing of content types to the server. It accommodates
*-tk, --token*=_<token>_::
A dotCMS token to use for authentication.

*-w*, *--watch*[=_watch_]::
When this option is enabled the tool watches for file changes within the push path If a change is detected the push operation gets triggered. The default watch interval is 2 seconds, but this can set passing an int value to this option.

// end::picocli-generated-man-section-options[]

// tag::picocli-generated-man-section-arguments[]
Expand Down
8 changes: 6 additions & 2 deletions tools/dotcms-cli/cli/docs/files-push.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,9 @@ files-push - *dotCMS Files push*
// tag::picocli-generated-man-section-synopsis[]
== Synopsis

*files push* [*-h*] [*--dry-run*] [*-ff*] [*-ra*] [*-rf*] [*--dotcms-url*=_<remoteURL>_]
[*--retry-attempts*=_<retryAttempts>_] [*-tk*=_<token>_] [_path_]
*files push* [*-h*] [*--dry-run*] [*-ff*] [*-ra*] [*-rf*] [*-w*[=_watch_]]
[*--dotcms-url*=_<remoteURL>_] [*--retry-attempts*=_<retryAttempts>_]
[*-tk*=_<token>_] [_path_]

// end::picocli-generated-man-section-synopsis[]

Expand Down Expand Up @@ -59,6 +60,9 @@ files-push - *dotCMS Files push*
*-tk, --token*=_<token>_::
A dotCMS token to use for authentication.

*-w*, *--watch*[=_watch_]::
When this option is enabled the tool watches for file changes within the push path If a change is detected the push operation gets triggered. The default watch interval is 2 seconds, but this can set passing an int value to this option.

// end::picocli-generated-man-section-options[]

// tag::picocli-generated-man-section-arguments[]
Expand Down
9 changes: 6 additions & 3 deletions tools/dotcms-cli/cli/docs/language-push.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ language-push - *Push languages*
// tag::picocli-generated-man-section-synopsis[]
== Synopsis

*language push* [*-h*] [*-dau*] [*--dry-run*] [*-ff*] [*-rl*] [*--byIso*=_<languageIso>_]
[*--dotcms-url*=_<remoteURL>_] [*--retry-attempts*=_<retryAttempts>_]
[*-tk*=_<token>_] [_path_]
*language push* [*-h*] [*-dau*] [*--dry-run*] [*-ff*] [*-rl*] [*-w*[=_watch_]]
[*--byIso*=_<languageIso>_] [*--dotcms-url*=_<remoteURL>_]
[*--retry-attempts*=_<retryAttempts>_] [*-tk*=_<token>_] [_path_]

// end::picocli-generated-man-section-synopsis[]

Expand Down Expand Up @@ -63,6 +63,9 @@ This command enables the pushing of languages to the server. It accommodates the
*-tk, --token*=_<token>_::
A dotCMS token to use for authentication.

*-w*, *--watch*[=_watch_]::
When this option is enabled the tool watches for file changes within the push path If a change is detected the push operation gets triggered. The default watch interval is 2 seconds, but this can set passing an int value to this option.

// end::picocli-generated-man-section-options[]

// tag::picocli-generated-man-section-arguments[]
Expand Down
5 changes: 4 additions & 1 deletion tools/dotcms-cli/cli/docs/push.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ push - *dotCMS global push*
// tag::picocli-generated-man-section-synopsis[]
== Synopsis

*push* [*-h*] [*-dau*] [*--dry-run*] [*-ff*] [*--dotcms-url*=_<remoteURL>_]
*push* [*-h*] [*-dau*] [*--dry-run*] [*-ff*] [*-w*[=_watch_]] [*--dotcms-url*=_<remoteURL>_]
[*--retry-attempts*=_<retryAttempts>_] [*-tk*=_<token>_] [_path_]

// end::picocli-generated-man-section-synopsis[]
Expand Down Expand Up @@ -56,6 +56,9 @@ push - *dotCMS global push*
*-tk, --token*=_<token>_::
A dotCMS token to use for authentication.

*-w*, *--watch*[=_watch_]::
When this option is enabled the tool watches for file changes within the push path If a change is detected the push operation gets triggered. The default watch interval is 2 seconds, but this can set passing an int value to this option.

// end::picocli-generated-man-section-options[]

// tag::picocli-generated-man-section-arguments[]
Expand Down
8 changes: 6 additions & 2 deletions tools/dotcms-cli/cli/docs/site-push.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,9 @@ site-push - *Push sites*
// tag::picocli-generated-man-section-synopsis[]
== Synopsis

*site push* [*-h*] [*-dau*] [*--dry-run*] [*-ff*] [*-fse*] [*-rs*] [*--dotcms-url*=_<remoteURL>_]
[*--retry-attempts*=_<retryAttempts>_] [*-tk*=_<token>_] [_path_]
*site push* [*-h*] [*-dau*] [*--dry-run*] [*-ff*] [*-fse*] [*-rs*] [*-w*[=_watch_]]
[*--dotcms-url*=_<remoteURL>_] [*--retry-attempts*=_<retryAttempts>_]
[*-tk*=_<token>_] [_path_]

// end::picocli-generated-man-section-synopsis[]

Expand Down Expand Up @@ -62,6 +63,9 @@ This command enables the pushing of sites to the server. It accommodates the spe
*-tk, --token*=_<token>_::
A dotCMS token to use for authentication.

*-w*, *--watch*[=_watch_]::
When this option is enabled the tool watches for file changes within the push path If a change is detected the push operation gets triggered. The default watch interval is 2 seconds, but this can set passing an int value to this option.

// end::picocli-generated-man-section-options[]

// tag::picocli-generated-man-section-arguments[]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
package com.dotcms.api.client.util;

import java.io.IOException;
import java.nio.file.FileSystems;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.StandardWatchEventKinds;
import java.nio.file.WatchEvent;
import java.nio.file.WatchKey;
import java.nio.file.WatchService;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.HashMap;
import java.util.Map;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import org.jboss.logging.Logger;

public class DirectoryWatcherService {

private final Logger logger = Logger.getLogger(DirectoryWatcherService.class);

private final Queue<WatchEvent<?>> eventQueue = new ConcurrentLinkedQueue<>();
private final Map<WatchKey, Path> keys = new HashMap<>();
private WatchService watchService;
private ScheduledExecutorService scheduler;

@SuppressWarnings("unchecked")
public void watch(final Path path, final long pollInterval, final boolean onlyShowLastEvent, final WatchEventConsumer eventConsumer) throws IOException, InterruptedException {
watchService = FileSystems.getDefault().newWatchService();
registerAll(path);

logger.debug("Watching directory: " + path);

scheduler = Executors.newScheduledThreadPool(1);
scheduler.scheduleAtFixedRate(() -> {
try {
processEvents(onlyShowLastEvent, eventConsumer);
} catch (IOException e) {
throw new RuntimeException(e);
} catch (InterruptedException | ExecutionException e) {
Thread.currentThread().interrupt();
}
}, pollInterval, pollInterval, TimeUnit.SECONDS);

while (true) {
WatchKey key = watchService.take();
Path dir = keys.get(key);

if (dir == null) {
logger.error("WatchKey not recognized!!");
continue;
}

for (WatchEvent<?> event : key.pollEvents()) {
WatchEvent.Kind<?> kind = event.kind();
if (kind == StandardWatchEventKinds.OVERFLOW) {
continue;
}

WatchEvent<Path> ev = (WatchEvent<Path>) event;
Path name = ev.context();
Path child = dir.resolve(name);

eventQueue.add(event);
logger.debug(kind.name() + ": " + child);

if (kind == StandardWatchEventKinds.ENTRY_CREATE) {
if (Files.isDirectory(child, LinkOption.NOFOLLOW_LINKS)) {
registerAll(child);
}
}
}

boolean valid = key.reset();
if (!valid) {
keys.remove(key);
if (keys.isEmpty()) {
logger.debug("All directories are inaccessible, stopping watch service.");
watchService.close();
break;
}
}
}
}

private void registerAll(final Path start) throws IOException {
Files.walkFileTree(start, new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
register(dir);
return FileVisitResult.CONTINUE;
}
});
}

private void register(Path dir) throws IOException {
WatchKey key = dir.register(watchService, StandardWatchEventKinds.ENTRY_CREATE,
StandardWatchEventKinds.ENTRY_DELETE, StandardWatchEventKinds.ENTRY_MODIFY);
keys.put(key, dir);
logger.debug("Registered: " + dir);
}

@SuppressWarnings("unchecked")
private void processEvents(final boolean onlyShowLastEvent, WatchEventConsumer eventConsumer)
throws IOException, InterruptedException, ExecutionException {
if (onlyShowLastEvent) {
WatchEvent<?> lastEvent = null;
WatchEvent<?> event;
while ((event = eventQueue.poll()) != null) {
lastEvent = event;
}
if (lastEvent != null) {
eventConsumer.accept((WatchEvent<Path>) lastEvent);
}
} else {
WatchEvent<?> event;
while ((event = eventQueue.poll()) != null) {
eventConsumer.accept((WatchEvent<Path>) event);
}
}
}

public void stopWatching() throws IOException {
if (scheduler != null) {
scheduler.shutdown();
}
if (watchService != null) {
watchService.close();
}
}

@FunctionalInterface
public interface WatchEventConsumer {
void accept(WatchEvent<Path> event)
throws IOException, InterruptedException, ExecutionException;
}
}


Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,20 @@
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.stream.Collectors;
import javax.enterprise.context.control.ActivateRequestContext;
import javax.enterprise.inject.Instance;
import javax.inject.Inject;
import picocli.CommandLine;

/**
* Global Push Command
* Represents a push command that is used to push Sites, Content Types, Languages, and Files to the
* server.
*/
Expand Down Expand Up @@ -74,18 +80,34 @@ public Integer call() throws Exception {
.sorted(Comparator.comparingInt(DotPush::getOrder))
.collect(Collectors.toList());

// Process each subcommand
for (var subCommand : pushCommandsSorted) {
// Usa ExecutorService for parallel execution of the subcommands
final ExecutorService executorService = Executors.newFixedThreadPool(pushCommandsSorted.size());
final List<Future<Integer>> futures = new ArrayList<>();

var cmdLine = createCommandLine(subCommand);
for (var subCommand : pushCommandsSorted) {
Callable<Integer> task = () -> {
var cmdLine = createCommandLine(subCommand);
return cmdLine.execute(args);
};
futures.add(executorService.submit(task));
}

// Use execute to parse the parameters with the subcommand
int exitCode = cmdLine.execute(args);
if (exitCode != CommandLine.ExitCode.OK) {
return exitCode;
// Wait for all subcommands to finish and check for errors
for (Future<Integer> future : futures) {
try {
int exitCode = future.get();
if (exitCode != CommandLine.ExitCode.OK) {
executorService.shutdownNow();
return exitCode;
}
} catch (InterruptedException | ExecutionException e) {
executorService.shutdownNow();
throw new RuntimeException("Error executing subcommand", e);
}
}

executorService.shutdown();

return CommandLine.ExitCode.OK;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ public Integer call() throws Exception {
if (workspace.isEmpty()) {
throw new IllegalArgumentException(
String.format("No valid workspace found at path: [%s]",
this.getPushMixin().path.toPath()));
this.getPushMixin().pushPath.toPath()));
}

File inputFile = this.getPushMixin().path().toFile();
Expand Down
Loading

0 comments on commit faf630d

Please sign in to comment.