Skip to content

Commit

Permalink
fix #3505 Error compiling AppCode.dll in Oqtane
Browse files Browse the repository at this point in the history
  • Loading branch information
tvatavuk committed Nov 29, 2024
1 parent 71ebab5 commit f59c52a
Show file tree
Hide file tree
Showing 2 changed files with 86 additions and 27 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ namespace ToSic.Sxc.Oqt.Server.Code.Internal;
[PrivateApi]
internal class AppCodeCompilerNetCore(LazySvc<IServerPaths> serverPaths, Generator<Compiler> compiler, IGlobalConfiguration globalConfiguration, SourceCodeHasher sourceCodeHasher) : AppCodeCompiler(globalConfiguration, sourceCodeHasher, connect: [serverPaths, compiler, sourceCodeHasher])
{
private readonly TryLockTryDo _lockAppCodeAssemblyProvider = new();

protected internal override AssemblyResult GetAppCode(string virtualPath, HotBuildSpecWithSharedSuffix spec)
{
var l = Log.Fn<AssemblyResult>($"{nameof(virtualPath)}: '{virtualPath}'; {spec}", timer: true);
Expand All @@ -26,9 +28,14 @@ protected internal override AssemblyResult GetAppCode(string virtualPath, HotBui

var (symbolsPath, assemblyPath) = GetAssemblyLocations(spec, sourceRootPath);
var dllName = Path.GetFileName(assemblyPath);
var assemblyResult = File.Exists(assemblyPath)
? new AssemblyResult(assembly: new SimpleUnloadableAssemblyLoadContext().LoadFromAssemblyPath(assemblyPath))
: compiler.New().GetCompiledAssemblyFromFolder(sourceFiles, assemblyPath, symbolsPath, dllName, spec);

var result = _lockAppCodeAssemblyProvider.Call(
conditionToGenerate: () => !File.Exists(assemblyPath) || new FileInfo(assemblyPath).Length == 0 || IsFileLocked(assemblyPath),
generator: () => compiler.New().GetCompiledAssemblyFromFolder(sourceFiles, assemblyPath, symbolsPath, dllName, spec),
cacheOrFallback: () => new AssemblyResult(assembly: new SimpleUnloadableAssemblyLoadContext().LoadFromAssemblyPath(assemblyPath))
);

var assemblyResult = result.Result;

var dicInfos = new Dictionary<string, string>
{
Expand All @@ -40,14 +47,13 @@ protected internal override AssemblyResult GetAppCode(string virtualPath, HotBui
["SymbolsPath"] = symbolsPath,
};

if (!assemblyResult.ErrorMessages.IsEmpty())
return l.ReturnAsError(new(errorMessages: assemblyResult.ErrorMessages, infos: dicInfos), assemblyResult.ErrorMessages);

// Compile ok
if (assemblyResult.ErrorMessages.IsEmpty())
{
LogAllTypes(assemblyResult.Assembly);
return l.ReturnAsOk(new(assembly: assemblyResult.Assembly, assemblyLocations: [symbolsPath, assemblyPath], infos: dicInfos));
}
LogAllTypes(assemblyResult.Assembly);
return l.ReturnAsOk(new(assembly: assemblyResult.Assembly, assemblyLocations: [symbolsPath, assemblyPath], infos: dicInfos));

return l.ReturnAsError(new(errorMessages: assemblyResult.ErrorMessages, infos: dicInfos), assemblyResult.ErrorMessages);
}
catch (Exception ex)
{
Expand All @@ -56,4 +62,35 @@ protected internal override AssemblyResult GetAppCode(string virtualPath, HotBui
return l.ReturnAsError(new(errorMessages: errorMessage), "error");
}
}

private static bool IsFileLocked(string filePath)
{
try
{
var fileInfo = new FileInfo(filePath);

// Check if the file is read-only
if (fileInfo.IsReadOnly)
return true;

// Try to open the file with FileShare.None to check if it is locked
using var stream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.None);
return !stream.CanRead;
}
catch (IOException)
{
// If an IOException is thrown, the file is locked
return true;
}
catch (UnauthorizedAccessException)
{
// If an UnauthorizedAccessException is thrown, the file is locked
return true;
}
catch (Exception)
{
// Handle any other exceptions that might occur
return true;
}
}
}
58 changes: 40 additions & 18 deletions Src/Sxc/ToSic.Sxc/Code/Internal/HotBuild/SourceCodeHasher.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System.Security.Cryptography;
using ToSic.Eav.Caching;
using ToSic.Eav.Context;
using ToSic.Eav.Plumbing;
using ToSic.Lib.DI;
using ToSic.Lib.Services;

Expand All @@ -14,6 +15,8 @@ public class SourceCodeHasher(LazySvc<IPlatformInfo> platform, MemoryCacheServic
private const bool UseSubfolders = true;
private const int BufferSize = 16 * 1024; // 16KB buffer
private const int CacheMinutes = 10;
private readonly TryLockTryDo _lockHashProvider = new();
private readonly TryLockTryDo _lockSourceFilesProvider = new();

public string GetHashString(string folderPath)
{
Expand All @@ -24,17 +27,26 @@ public string GetHashString(string folderPath)
if (memoryCacheService.TryGet<string>(cacheKey, out var fromCache))
return l.Return(fromCache, "from cache");

var files = GetSourceFilesInFolder(folderPath);
l.A($"{files.Length} files found");
var result = _lockHashProvider.Call(
conditionToGenerate: () => !memoryCacheService.TryGet<string>(cacheKey, out _),
generator: () =>
{
var files = GetSourceFilesInFolder(folderPath);
l.A($"{files.Length} files found");

var computeHashStringForFiles = BitConverter.ToString(ComputeHashForFiles(files)).Replace("-", "").ToLower();
l.A("hash string computed");
var computeHashStringForFiles = BitConverter.ToString(ComputeHashForFiles(files)).Replace("-", "").ToLower();
l.A("hash string computed");

memoryCacheService.SetNew(cacheKey, files, p => p
.SetSlidingExpiration(CacheMinutes * 60)
.WatchCacheKeys(new[] { SourceFilesInFolderCacheKey(folderPath) }));
memoryCacheService.SetNew(cacheKey, computeHashStringForFiles, p => p
.SetSlidingExpiration(CacheMinutes * 60)
.WatchCacheKeys([SourceFilesInFolderCacheKey(folderPath)]));

return l.ReturnAsOk(computeHashStringForFiles);
return computeHashStringForFiles;
},
cacheOrFallback: () => memoryCacheService.Get<string>(cacheKey)
);

return l.ReturnAsOk(result.Result);
}

internal string[] GetSourceFilesInFolder(string fullPath)
Expand All @@ -46,18 +58,28 @@ internal string[] GetSourceFilesInFolder(string fullPath)
if (memoryCacheService.TryGet<string[]>(cacheKey, out var fromCache))
return l.Return(fromCache, "from cache");

var files = Directory.GetFiles(fullPath, $"*{CsFiles}", UseSubfolders ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly);
l.A($"{files.Length} files found with {nameof(UseSubfolders)}: {UseSubfolders}");
Array.Sort(files, StringComparer.Ordinal); // sort the array of file paths before processing to ensure hashing deterministic behavior.
l.A("sorted files");

memoryCacheService.SetNew(cacheKey, files, p => p
.SetSlidingExpiration(CacheMinutes * 60)
.WatchFolders(new Dictionary<string, bool> { { fullPath, UseSubfolders } }));

return l.ReturnAsOk(files);
var result = _lockSourceFilesProvider.Call(
conditionToGenerate: () => !memoryCacheService.TryGet<string[]>(cacheKey, out _),
generator: () =>
{
var files = Directory.GetFiles(fullPath, $"*{CsFiles}", UseSubfolders ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly);
l.A($"{files.Length} files found with {nameof(UseSubfolders)}: {UseSubfolders}");
Array.Sort(files, StringComparer.Ordinal); // sort the array of file paths before processing to ensure hashing deterministic behavior.
l.A("sorted files");

memoryCacheService.SetNew(cacheKey, files, p => p
.SetSlidingExpiration(CacheMinutes * 60)
.WatchFolders(new Dictionary<string, bool> { { fullPath, UseSubfolders } }));

return files;
},
cacheOrFallback: () => memoryCacheService.Get<string[]>(cacheKey)
);

return l.ReturnAsOk(result.Result);
}


private string FolderHashCacheKey(string fullPath) => $"Sxc-FolderHash-{fullPath}";

private string SourceFilesInFolderCacheKey(string fullPath) => $"Sxc-SourceFilesInFolder-{fullPath}";
Expand Down

0 comments on commit f59c52a

Please sign in to comment.