jamesfredley commented on code in PR #16631:
URL: https://github.com/apache/grails-core/pull/16631#discussion_r4371526281


##########
grails-datamapping-core/src/cli/groovy/org/apache/grails/data/core/cli/querytail/QueryTailFileReader.groovy:
##########
@@ -0,0 +1,259 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you 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
+ *
+ *    https://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 org.apache.grails.data.core.cli.querytail
+
+import java.nio.charset.StandardCharsets
+
+import groovy.transform.CompileStatic
+
+import org.apache.grails.data.core.querytail.QueryTailRecord
+
+/**
+ * Follows a capture file the way {@code tail -f} does: each poll returns the 
records written
+ * since the last one, up to a {@link #CHUNK chunk} of the file at a time, so 
that a capture of any
+ * size is read in pieces; a reader that is {@link #isBehind() behind} has 
more to read without
+ * waiting. A line that has no terminator yet is left for the next poll, so a 
record the
+ * application is still writing is never read in half, and a line longer than 
a chunk is dropped
+ * up to its end, as a line that is not a record is. An application that 
starts again
+ * truncates the file and names its new run in the first line; when that line 
names another run,
+ * or the file shrinks, reading starts over from the beginning. The run tells 
a restart apart from
+ * a file that grew past where the reader was before it was next polled. A 
file truncated between
+ * being measured and being read, so that the read comes up short, is a 
restart too: it is read again
+ * from the beginning in the same poll. A poll that finds the file as long as 
it left it reads nothing,
+ * not even its first line, so an idle command costs nothing; a restart that 
has written nothing yet
+ * is noticed with its first statement.
+ *
+ * @since 8.1
+ */
+@CompileStatic
+class QueryTailFileReader implements Closeable {
+
+    private static final byte NEWLINE = (byte) 10
+
+    /** How much of the file the first line is looked for in; a header is far 
shorter. */
+    private static final int HEADER_LIMIT = 256
+
+    /** How much of the file one poll reads at most, in bytes. */
+    public static final int CHUNK = 1024 * 1024
+
+    final File file
+    final int chunk
+    private long position
+    private long measured = -1L
+    private String run
+    private boolean restarted
+    private boolean behind
+    private boolean skipping
+
+    /**
+     * @param file the capture file
+     * @param fromStart whether to read what the file already holds, or only 
what is written from now on
+     */
+    QueryTailFileReader(File file, boolean fromStart) {
+        this(file, fromStart, CHUNK)
+    }
+
+    /**
+     * @param file the capture file
+     * @param fromStart whether to read what the file already holds, or only 
what is written from now on
+     * @param chunk how much of the file one poll reads at most, in bytes
+     */
+    QueryTailFileReader(File file, boolean fromStart, int chunk) {
+        this.file = file
+        this.chunk = chunk
+        this.position = fromStart ? 0 : file.length()
+        this.run = file.exists() ? readRun() : null
+        // opened in the middle of a line being written: the rest of that line 
is not a record
+        this.skipping = position > 0 && !endsLine(readFrom(position - 1, 
position))
+    }
+
+    /**
+     * @return the records written since the last poll, or since the reader 
was opened, up to a chunk of the file
+     */
+    List<QueryTailRecord> poll() {
+        restarted = false
+        behind = false
+        // a read that comes up short is retried once, from the start; the 
next poll takes it from there
+        for (int attempt = 0; attempt < 2; attempt++) {
+            long length = file.length()
+            if (length == position && length == measured) {

Review Comment:
   If the application restarts between polls and the new capture is the same 
number of bytes, this returns before it reads the new run header. `poll()` then 
reports no records and no restart, so the summary stays on the old run.
   
   Check the header, or another change marker, before treating an unchanged 
length as idle.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to