Created
September 10, 2020 11:02
-
-
Save bennadel/ae361f22e93a0ea2ba1110660c6585ab to your computer and use it in GitHub Desktop.
Deleting Temporary Upload Files In Our K8 Operational Readiness Probe In Lucee CFML 5.3.6.61
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
component | |
output = false | |
hint = "I help log JVM system data for the health-probe." | |
{ | |
// ... truncated code ... | |
// --- | |
// PUBLIC METHODS. | |
// --- | |
/** | |
* I log the operations probe / health check for the server. The return value | |
* indicates whether or not the probe should be kept online (true = online); or, if it | |
* should be failed out of rotation. | |
*/ | |
public boolean function logProbeRequest() { | |
// NOTE: All of the runSafelyAsync() calls are spawned in a CFThread and | |
// synchronized internally such that overlapping health-probe calls will not | |
// trigger competing system measurements. | |
// ... truncated code ... | |
if ( shouldMeasureTempDirectory() ) { | |
runSafelyAsync( "measureTempDirectory" ); | |
} | |
// CAUTION: This is a "hack" that we're putting in place because Lucee's native | |
// background-task seems to be failing and we don't know why. As such, we're | |
// going to see if we can fill in the gap while we continue to debug the issue. | |
if ( shouldDeleteOldTempFiles() ) { | |
runSafelyAsync( "deleteOldTempFiles" ); | |
} | |
// ... truncated code ... | |
} | |
// --- | |
// PRIVATE METHODS. | |
// --- | |
/** | |
* I delete temporary upload files from the server's temp directory. | |
*/ | |
private void function deleteOldTempFiles() { | |
var fileCount = tempDirectoryHelper.deleteOldTempFiles( | |
filter = "tmp-*.upload", | |
ageInMinutes = 15 | |
); | |
datadog.increment( | |
key = getMetricName( "temp-directory.files-deleted" ), | |
magnitude = fileCount, | |
tags = tags | |
); | |
} | |
// ... truncated code ... | |
/** | |
* I record the current Lucee CFML temp-directory usage for this pod. | |
*/ | |
private void function measureTempDirectory() { | |
var directoryStats = tempDirectoryHelper.getStats(); | |
datadog.gauge( | |
key = getMetricName( "temp-directory.total-size" ), | |
value = directoryStats.size, | |
tags = tags | |
); | |
datadog.gauge( | |
key = getMetricName( "temp-directory.total-count" ), | |
value = directoryStats.count, | |
tags = tags | |
); | |
} | |
// ... truncated code ... | |
/** | |
* I execute and synchronize access to the given measurement method. This ensures | |
* that the act of running the given method doesn't, in and of itself, cause the | |
* health probe to fail. | |
*/ | |
private void function runSafelyAsync( required string methodName ) { | |
try { | |
thread | |
name = "probe.#methodName#-thread" | |
action = "run" | |
methodName = methodName | |
{ | |
try { | |
// Now that this method is being invoked asynchronously to the probe | |
// request, there's a chance that this measurement may overlap with | |
// a subsequent measurement. As such, let's synchronize the | |
// measurement operation(s) so that only one-of-a-type can be | |
// executing at a time. | |
lock | |
name = "probe.#methodName#-lock" | |
type = "exclusive" | |
timeout = 1 | |
throwOnTimeout = false | |
{ | |
invoke( variables, methodName ); | |
} // END: Lock. | |
} catch ( any error ) { | |
debugLogger.error( error, "Error calling [#methodName#()] in the operations probe." ); | |
} | |
} // END: Thread. | |
} catch ( any error ) { | |
debugLogger.error( error, "Error spawning async thread in the operations probe." ); | |
} | |
} | |
// ... truncated code ... | |
/** | |
* I check to see if the current machine should attempt to delete old files in the | |
* server's temp-directory. | |
*/ | |
private boolean function shouldDeleteOldTempFiles() { | |
return( featureFlagService.getFeatureByUserID( hostName, "OPERATIONS--cfprojectsapi--delete-old-temp-files" ) ); | |
} | |
// ... truncated code ... | |
/** | |
* I check to see if the current machine should be measuring temp-directory stats as | |
* part of the metrics that is emits on each health-check. | |
*/ | |
private boolean function shouldMeasureTempDirectory() { | |
return( featureFlagService.getFeatureByUserID( hostName, "OPERATIONS--cfprojectsapi--measure-temp-directory-in-health-check" ) ); | |
} | |
// ... truncated code ... | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
component | |
output = false | |
hint = "I provide helper methods for Lucee's temp-directory." | |
{ | |
/** | |
* I initialize the temp-directory helper using the given directory as the target | |
* directory. | |
* | |
* @tempDirectoryPath I am the temp-directory for the server. | |
*/ | |
public void function init( string tempDirectoryPath = getTempDirectory() ) { | |
variables.tempDirectoryPath = arguments.tempDirectoryPath; | |
// Ensure that the temp-directory ends with a slash - we'll need this when we | |
// programmatically generate file-paths later on. | |
if ( ! variables.tempDirectoryPath.reFind( "[\\/]$" ) ) { | |
variables.tempDirectoryPath &= "/"; | |
} | |
} | |
// --- | |
// PUBLIC METHODS. | |
// --- | |
/** | |
* I delete files from the temp-directory that match the given filter and are older | |
* than the given age in minutes. I return the number of files deleted. | |
* | |
* CAUTION: If a File IO error is thrown, it halts the current process and the error | |
* bubbles up to the calling context. | |
* | |
* @filter I am the filter to pass to directoryList(). | |
* @ageInMinutes I am the positive age in minutes after which a file will be deleted. | |
*/ | |
public numeric function deleteOldTempFiles( | |
required string filter, | |
required numeric ageInMinutes | |
) { | |
var filesQuery = directoryList( | |
path = tempDirectoryPath, | |
listInfo = "query", | |
filter = filter, | |
sort = "dateLastModified ASC", | |
type = "file" | |
); | |
var cutoffAt = now().add( "n", -ageInMinutes ); | |
var deletedCount = 0; | |
loop query = filesQuery { | |
if ( filesQuery.dateLastModified < cutoffAt ) { | |
fileDelete( tempDirectoryPath & filesQuery.name ); | |
deletedCount++; | |
} else { | |
// Since we sorted the file-query by date-descending, we know that once | |
// we hit a file that is relatively "new", we've exhausted all of the old | |
// files. Everything after this is going to be "new" and can be ignored. | |
break; | |
} | |
} | |
return( deletedCount ); | |
} | |
/** | |
* I return the size and count stats for the temp-directory. | |
* | |
* CAUTION: Any sub-directories that are part of the temp-directory will be counted | |
* as a single item of size zero bytes. Right now, I think our problem is related to | |
* temporary files, but we can re-evaluate later. | |
*/ | |
public struct function getStats() { | |
var totalCount = 0; | |
var totalSize = 0; | |
if ( directoryExists( tempDirectoryPath ) ) { | |
var filesQuery = directoryList( | |
path = tempDirectoryPath, | |
listInfo = "query" | |
); | |
totalCount = filesQuery.recordCount; | |
loop query = filesQuery { | |
totalSize += size; | |
} | |
} | |
return({ | |
count: totalCount, | |
size: totalSize | |
}); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment