Skip to content

Commit 8e6e140

Browse files
authored
Merge pull request #318 from intersystems/performance
Various performance fixes
2 parents 6c331f4 + 35d6c83 commit 8e6e140

File tree

7 files changed

+158
-31
lines changed

7 files changed

+158
-31
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1313
- Deletion of files in locked environment is now suppressed (#302)
1414
- Failed to import file VS Code popup no longer shows up after overwriting file on server once (#264)
1515
- Don't automatically stage files added to source control (#303)
16+
- Performance improvements (#269, #315)
1617
- Checkout of branches whose names contain slashes via Web UI no longer fails (#295)
1718

1819
## [2.3.0] - 2023-12-06

cls/SourceControl/Git/Change.cls

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,8 +78,19 @@ ClassMethod IsUncommitted(Filename, ByRef ID) As %Boolean
7878
}
7979

8080
/// Goes through Uncommitted queue and removes any items of action 'edit' or 'add' which are ReadOnly or non-existent on the filesystem
81-
ClassMethod RefreshUncommitted(Display = 0, IncludeRevert = 0, Output gitFiles) As %Status
81+
ClassMethod RefreshUncommitted(Display = 0, IncludeRevert = 0, Output gitFiles, Force As %Boolean = 0) As %Status
8282
{
83+
set lock = $System.AutoLock.Lock("^SourceControl.Git.Refresh",,10)
84+
if lock = $$$NULLOREF {
85+
quit $$$ERROR($$$GeneralError,"Unable to get exclusive lock for refresh of uncommitted changes.")
86+
}
87+
if 'Force {
88+
// 10-second throttle on RefreshUncommitted
89+
if $zdatetime($ztimestamp,-2) - $Get(^IRIS.Temp.gitsourcecontrol("Refresh"),0) < 10 {
90+
merge gitFiles = ^IRIS.Temp.gitsourcecontrol("LastUncommitted")
91+
quit $$$OK
92+
}
93+
}
8394
kill gitFiles
8495

8596
// files from the uncommitted queue
@@ -124,6 +135,8 @@ ClassMethod RefreshUncommitted(Display = 0, IncludeRevert = 0, Output gitFiles)
124135
}
125136
set filename=$order(gitFiles(filename),1,details)
126137
}
138+
set ^IRIS.Temp.gitsourcecontrol("Refresh") = $zdatetime($ztimestamp,-2)
139+
merge ^IRIS.Temp.gitsourcecontrol("LastUncommitted") = gitFiles
127140
quit sc
128141
}
129142

cls/SourceControl/Git/File.cls

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
/// Has a cache of file internal/external name mappings as of LastModifiedTime.
2+
Class SourceControl.Git.File Extends %Persistent
3+
{
4+
5+
Property ExternalName As %String(MAXLEN = "") [ Required ];
6+
7+
Property ExternalNameHash As %String [ Calculated, SqlComputeCode = {set {*} = $System.Encryption.SHAHash(256,{ExternalName})}, SqlComputed ];
8+
9+
Property InternalName As %String(MAXLEN = 255) [ Required ];
10+
11+
Property LastModifiedTime As %String [ Required ];
12+
13+
Index InternalName On InternalName;
14+
15+
Index ExternalNameHash On ExternalNameHash [ Unique ];
16+
17+
ClassMethod ExternalNameToInternalName(ExternalName As %String) As %String
18+
{
19+
set internalName = ""
20+
if ##class(%File).Exists(ExternalName) {
21+
set lastModified = ##class(%Library.File).GetFileDateModified(ExternalName)
22+
set hash = $System.Encryption.SHAHash(256,ExternalName)
23+
if ..ExternalNameHashExists(hash,.id) {
24+
set inst = ..%OpenId(id,,.sc)
25+
$$$ThrowOnError(sc)
26+
if inst.LastModifiedTime = lastModified {
27+
quit inst.InternalName
28+
} else {
29+
set inst.LastModifiedTime = lastModified
30+
}
31+
} else {
32+
set inst = ..%New()
33+
set inst.ExternalName = ExternalName
34+
set inst.LastModifiedTime = lastModified
35+
}
36+
new %SourceControl //don't trigger source hooks with this test load to get the Name
37+
set sc=$system.OBJ.Load(ExternalName,"-d",,.outName,1)
38+
if (($data(outName)=1) || ($data(outName) = 11 && ($order(outName(""),-1) = $order(outName(""))))) && ($zconvert(##class(SourceControl.Git.Utils).Type(outName),"U") '= "CSP") {
39+
set internalName = outName
40+
set inst.InternalName = internalName
41+
$$$ThrowOnError(inst.%Save())
42+
}
43+
}
44+
quit internalName
45+
}
46+
47+
Storage Default
48+
{
49+
<Data name="FileDefaultData">
50+
<Value name="1">
51+
<Value>%%CLASSNAME</Value>
52+
</Value>
53+
<Value name="2">
54+
<Value>ExternalName</Value>
55+
</Value>
56+
<Value name="3">
57+
<Value>InternalName</Value>
58+
</Value>
59+
<Value name="4">
60+
<Value>LastModifiedTime</Value>
61+
</Value>
62+
</Data>
63+
<DataLocation>^SourceControl.Git.FileD</DataLocation>
64+
<DefaultData>FileDefaultData</DefaultData>
65+
<IdLocation>^SourceControl.Git.FileD</IdLocation>
66+
<IndexLocation>^SourceControl.Git.FileI</IndexLocation>
67+
<StreamLocation>^SourceControl.Git.FileS</StreamLocation>
68+
<Type>%Storage.Persistent</Type>
69+
}
70+
71+
}

cls/SourceControl/Git/Settings.cls

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ Method OnAfterConfigure() As %Boolean
130130
// using work queue manager ensures proper OS user context when running ssh-keygen
131131
set workMgr = $System.WorkMgr.%New("")
132132
$$$ThrowOnError(workMgr.Queue("##class(SourceControl.Git.Utils).GenerateSSHKeyPair"))
133-
$$$ThrowOnError(workMgr.Sync())
133+
$$$ThrowOnError(workMgr.WaitForComplete())
134134
set pubKeyName = ..privateKeyFile_".pub"
135135
if ##class(%File).Exists(pubKeyName) {
136136
set pubStream = ##class(%Stream.FileCharacter).%OpenId(pubKeyName,,.sc)
@@ -158,7 +158,7 @@ Method OnAfterConfigure() As %Boolean
158158
// using work queue manager ensures proper OS user context/file ownership
159159
set workMgr = $System.WorkMgr.%New("")
160160
$$$ThrowOnError(workMgr.Queue("##class(SourceControl.Git.Utils).Init"))
161-
$$$ThrowOnError(workMgr.Sync())
161+
$$$ThrowOnError(workMgr.WaitForComplete())
162162
do ##class(SourceControl.Git.Utils).EmptyInitialCommit()
163163
} elseif (value = 2) {
164164
set response = ##class(%Library.Prompt).GetString("Git remote URL (note: if authentication is required, use SSH, not HTTPS):",.remote,,,,defaultPromptFlag)
@@ -171,7 +171,7 @@ Method OnAfterConfigure() As %Boolean
171171
// using work queue manager ensures proper OS user context/file ownership
172172
set workMgr = $System.WorkMgr.%New("")
173173
$$$ThrowOnError(workMgr.Queue("##class(SourceControl.Git.Utils).Clone",remote))
174-
$$$ThrowOnError(workMgr.Sync())
174+
$$$ThrowOnError(workMgr.WaitForComplete())
175175
}
176176
}
177177
}

cls/SourceControl/Git/Utils.cls

Lines changed: 62 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -285,7 +285,7 @@ ClassMethod Revert(InternalName As %String) As %Status
285285
set filename = ..FullExternalName(.InternalName)
286286
do ..RunGitCommand("checkout", .errStream, .outStream, "--", filename)
287287
$$$QuitOnError(##class(SourceControl.Git.Change).RemoveUncommitted(filename,0,1))
288-
$$$QuitOnError(##class(SourceControl.Git.Change).RefreshUncommitted(0,1))
288+
$$$QuitOnError(##class(SourceControl.Git.Change).RefreshUncommitted(0,1,,1))
289289
$$$QuitOnError(..ImportItem(InternalName,1))
290290
quit $$$OK
291291
}
@@ -299,7 +299,7 @@ ClassMethod Commit(InternalName As %String, Message As %String = "example commit
299299
do ..RunGitWithArgs(.errStream, .outStream, "commit", "--author", author, "-m", Message, filename)
300300
do ..PrintStreams(outStream, outStream)
301301
$$$QuitOnError(##class(SourceControl.Git.Change).RemoveUncommitted(filename))
302-
$$$QuitOnError(##class(SourceControl.Git.Change).RefreshUncommitted())
302+
$$$QuitOnError(##class(SourceControl.Git.Change).RefreshUncommitted(,,,1))
303303
quit $$$OK
304304
}
305305

@@ -453,7 +453,7 @@ ClassMethod GenerateSSHKeyPair() As %Status
453453
do ##class(%File).CreateDirectoryChain(dir)
454454
set outLog = ##class(%Library.File).TempFilename()
455455
set errLog = ##class(%Library.File).TempFilename()
456-
do $zf(-100,"/SHELL /STDOUT="_$$$QUOTE(outLog)_" /STDERR="_$$$QUOTE(errLog),
456+
do $zf(-100,"/STDOUT="_$$$QUOTE(outLog)_" /STDERR="_$$$QUOTE(errLog),
457457
"ssh-keygen",
458458
"-t","ed25519",
459459
"-C",email,
@@ -776,10 +776,13 @@ ClassMethod Type(InternalName As %String) As %String
776776
} else {
777777
set type ="csp"
778778
}
779+
if (type = "csp") {
780+
quit type
781+
}
779782
}
780783

781784
// For an abstract document, use the GetOther() method to try to determine its "real" class
782-
If ##class(%RoutineMgr).UserType(InternalName,.docclass,.doctype) {
785+
If ..UserTypeCached(InternalName,.docclass,.doctype) {
783786
// Check for a real abstract document subclass (or GetOther() may not work)
784787
If $classmethod(docclass,"%IsA","%Studio.AbstractDocument") && $classmethod(docclass,"%Extends","Ens.Util.AbstractDocument") {
785788
// Grab the actual name
@@ -966,7 +969,7 @@ ClassMethod IsInSourceControl(InternalName As %String, ByRef sourceControlItem A
966969

967970
ClassMethod FullExternalName(ByRef InternalName As %String, ByRef MappingExists As %Boolean) As %String [ CodeMode = expression ]
968971
{
969-
##class(%File).NormalizeFilename(..TempFolder()_..ExternalName(.InternalName, .MappingExists))
972+
..TempFolder()_..ExternalName(.InternalName, .MappingExists)
970973
}
971974

972975
ClassMethod NormalizeInternalName(ByRef name As %String) As %String
@@ -1106,7 +1109,7 @@ ClassMethod ImportItem(InternalName As %String, force As %Boolean = 0, verbose A
11061109
#dim sc as %Status = $$$OK
11071110

11081111
if ..IsRoutineOutdated(InternalName) || force {
1109-
if ##class(%RoutineMgr).UserType(InternalName,.docclass,.doctype) {
1112+
if ..UserTypeCached(InternalName,.docclass,.doctype) {
11101113
set routineMgr = ##class(%RoutineMgr).%OpenId(InternalName)
11111114
do routineMgr.Code.Rewind()
11121115
set source = ##class(%Stream.FileCharacter).%OpenId(filename,,.sc)
@@ -1169,7 +1172,7 @@ ClassMethod ListItemsInFiles(ByRef itemList, ByRef err) As %Status
11691172
set mappedFilePath = ##class(%File).NormalizeFilename(mappedRelativePath, ..TempFolder())
11701173

11711174
if (##class(%File).DirectoryExists(mappedFilePath)){
1172-
if ##class(%Library.RoutineMgr).UserType("foo."_mappingFileType) {
1175+
if ..UserTypeCached("foo."_mappingFileType) {
11731176
set fileSpec = "*."_$zcvt(mappingFileType,"L")_";*."_$zconvert(mappingFileType,"U")
11741177
set files = ##class(%Library.File).FileSetFunc(mappedFilePath,fileSpec)
11751178
while files.%Next() {
@@ -1268,7 +1271,7 @@ ClassMethod ImportRoutines(force As %Boolean = 0) As %Status
12681271
if ##class(%File).Exists(filename) && '##class(%File).Delete(filename) {
12691272
set ec = $$$ADDSC(ec, ..MakeError("Error while removing "_item))
12701273
}
1271-
}elseif ##class(%Library.RoutineMgr).UserType(item) {
1274+
}elseif ..UserTypeCached(item) {
12721275
set ec = $$$ADDSC(ec, ##class(%Library.RoutineMgr).Delete(item))
12731276
} else {
12741277
set deleted = 0
@@ -1485,8 +1488,20 @@ ClassMethod RunGitCommandWithInput(command As %String, inFile As %String = "", O
14851488
set errLog = ##class(%Library.File).TempFilename()
14861489

14871490
set command = $extract(..GitBinPath(),2,*-1)
1488-
// Need /SHELL on Linux to avoid permissions errors trying to use root's config
1489-
set returnCode = $zf(-100,"/SHELL /STDOUT="_$$$QUOTE(outLog)_" /STDERR="_$$$QUOTE(errLog)_$case(inFile, "":"", :" /STDIN="_$$$QUOTE(inFile)),command,newArgs...)
1491+
set baseArgs = "/STDOUT="_$$$QUOTE(outLog)_" /STDERR="_$$$QUOTE(errLog)_$case(inFile, "":"", :" /STDIN="_$$$QUOTE(inFile))
1492+
try {
1493+
// Inject instance manager directory as global git config home directory
1494+
// On Linux, this avoids trying to use /root/.config/git/attributes for global git config
1495+
set env("XDG_CONFIG_HOME") = ##class(%File).ManagerDirectory()
1496+
set returnCode = $zf(-100,"/ENV=env... "_baseArgs,command,newArgs...)
1497+
} catch e {
1498+
if $$$isWINDOWS {
1499+
set returnCode = $zf(-100,baseArgs,command,newArgs...)
1500+
} else {
1501+
// If can't inject XDG_CONFIG_HOME (older IRIS version), need /SHELL on Linux to avoid permissions errors trying to use root's config
1502+
set returnCode = $zf(-100,"/SHELL "_baseArgs,command,newArgs...)
1503+
}
1504+
}
14901505

14911506
set errStream = ##class(%Stream.FileCharacter).%OpenId(errLog,,.sc)
14921507
set outStream = ##class(%Stream.FileCharacter).%OpenId(outLog,,.sc)
@@ -1538,10 +1553,10 @@ ClassMethod Name(InternalName As %String, ByRef MappingExists As %Boolean) As %S
15381553
set relativePath = context.ResourceReference.Processor.OnItemRelativePath(InternalName)
15391554
quit relativePath
15401555
}
1541-
set usertype=$system.CLS.IsMthd("%Library.RoutineMgr","UserType")
15421556

15431557
// For an abstract document, use the GetOther() method to try to determine its "real" class
1544-
if usertype,##class(%RoutineMgr).UserType(InternalName,.docclass,.doctype) {
1558+
if ..UserTypeCached(InternalName,.docclass,.doctype) {
1559+
set usertype = 1
15451560
// Check for a real abstract document subclass (or GetOther() may not work)
15461561
if $classmethod(docclass,"%IsA","%Studio.AbstractDocument") && $classmethod(docclass,"%Extends","Ens.Util.AbstractDocument") {
15471562
// Grab the actual name
@@ -1552,9 +1567,11 @@ ClassMethod Name(InternalName As %String, ByRef MappingExists As %Boolean) As %S
15521567
set InternalName = actualName
15531568
}
15541569
}
1570+
} else {
1571+
set usertype = 0
15551572
}
15561573

1557-
if '##class(%Library.RoutineMgr).UserType(InternalName) && $$CheckProtect^%qccServer(InternalName) {
1574+
if 'usertype && $$CheckProtect^%qccServer(InternalName) {
15581575
quit ""
15591576
}
15601577

@@ -1631,7 +1648,7 @@ ClassMethod Name(InternalName As %String, ByRef MappingExists As %Boolean) As %S
16311648
set InternalName=$extract(InternalName,$length(p)+2,*)
16321649
quit $translate(found_$translate(InternalName,"%","_"),"\","/")
16331650

1634-
} elseif ext="CLS"||(ext="PRJ")||(usertype&&(##class(%RoutineMgr).UserType(InternalName))) {
1651+
} elseif ext="CLS"||(ext="PRJ")||usertype {
16351652
set nam=$replace(nam,"%", ..PercentClassReplace())
16361653
if default{
16371654
set nam=$translate(nam,".","/")
@@ -1647,6 +1664,31 @@ ClassMethod Name(InternalName As %String, ByRef MappingExists As %Boolean) As %S
16471664
}
16481665
}
16491666

1667+
/// Implementation copied from %Library.RoutineMgr, but with results cached in a PPG.
1668+
ClassMethod UserTypeCached(Name As %String, ByRef Class As %String, ByRef StudioType As %String, ByRef Schema As %String, ByRef StudioIcon As %Integer) As %Boolean
1669+
{
1670+
Set ext=$zconvert($piece(Name,".",*),"U") If ext="" Quit 0
1671+
If $Data(^||UserTypeCache(ext,"NotUserType"))#2 {
1672+
Quit 0
1673+
}
1674+
If $Data(^||UserTypeCache(ext),data)#2 {
1675+
Set Class = $Get(^||UserTypeCache(ext,"Class"))
1676+
Set StudioType=$list(data),Schema=$listget(data,3),StudioIcon=+$listget(data,4)
1677+
Quit 1
1678+
}
1679+
Do StudioDocument^%SYS.cspServer2(.document)
1680+
Set Class="",StudioType="",Schema=""
1681+
For Set Class=$order(document(Class)) Quit:Class=""||($data(document(Class,ext),data))
1682+
If Class="" {
1683+
Set ^||UserTypeCache(ext,"NotUserType") = 1
1684+
Quit 0
1685+
}
1686+
Set StudioType=$list(data),Schema=$listget(data,3),StudioIcon=+$listget(data,4)
1687+
Set ^||UserTypeCache(ext) = data
1688+
Set ^||UserTypeCache(ext,"Class") = Class
1689+
Quit 1
1690+
}
1691+
16501692
/*
16511693
NameToInternalName(name): given a Unix-style slash path relative to repo root,
16521694
returns the internal name for that file (e.g., cls/SourceControl/Git/Utils.cls -> SourceControl.Git.Utils.CLS)
@@ -1658,21 +1700,16 @@ ClassMethod NameToInternalName(Name, IgnorePercent = 1, IgnoreNonexistent = 1, V
16581700
set context = ##class(SourceControl.Git.PackageManagerContext).%Get()
16591701
if (context.IsInGitEnabledPackage) {
16601702
if ($zconvert(Name,"U")'[$zconvert(context.Package.Root,"U")) {
1661-
set Name = ##class(%File).NormalizeFilename(context.Package.Root_Name)
1703+
set Name = context.Package.Root_Name
16621704
}
16631705
} elseif ($zconvert(Name,"U")'[$zconvert($$$SourceRoot,"U")) {
1664-
set Name = ##class(%File).NormalizeFilename(..TempFolder()_Name)
1706+
set Name = ..TempFolder()_Name
16651707
}
16661708
if (##class(%File).Exists(Name)) {
1667-
new %SourceControl //don't trigger source hooks with this test load to get the Name
1668-
set sc=$system.OBJ.Load(Name,"-d",,.outName,1)
1669-
if (($data(outName)=1) || ($data(outName) = 11 && ($order(outName(""),-1) = $order(outName(""))))) && ($zconvert(..Type(outName),"U") '= "CSP") {
1670-
//only set if a single Name was returned ... ignore multi-item files
1671-
set InternalName=outName
1672-
if (context.IsInGitEnabledPackage) {
1673-
// Don't need mappings!
1674-
return ..NormalizeInternalName(InternalName)
1675-
}
1709+
set InternalName = ##class(SourceControl.Git.File).ExternalNameToInternalName(Name)
1710+
if (InternalName '= "") && (context.IsInGitEnabledPackage) {
1711+
// Don't need mappings!
1712+
return ..NormalizeInternalName(InternalName)
16761713
}
16771714
} else {
16781715
// check for file in uncommitted queue

cls/SourceControl/Git/WebUIDriver.cls

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ Class SourceControl.Git.WebUIDriver
33

44
ClassMethod HandleRequest(pagePath As %String, InternalName As %String = "", Output handled As %Boolean = 0, Output %data As %Stream.Object)
55
{
6+
do %session.Unlock()
67
set context = ##class(SourceControl.Git.PackageManagerContext).ForInternalName(InternalName)
78
kill %data
89
#dim %response as %CSP.Response
@@ -99,6 +100,8 @@ ClassMethod HandleRequest(pagePath As %String, InternalName As %String = "", Out
99100
set stdin = $piece(args(1),$char(10),2,*)
100101
set args(1) = $piece(args(1),$char(10))
101102
}
103+
set readOnlyCommands = $listbuild("branch","tag","log","ls-files","ls-tree","show","status","diff")
104+
set baseCommand = $Piece(args(1)," ")
102105

103106
set gitArgs($increment(gitArgs)) = "color.ui=true"
104107

@@ -154,7 +157,9 @@ ClassMethod HandleRequest(pagePath As %String, InternalName As %String = "", Out
154157
do %data.WriteLine("Git-Stderr-Length: " _ (errStream.Size + nLines))
155158
do %data.Write("Git-Return-Code: " _ returnCode) // No ending newline expected
156159
do %data.Rewind()
157-
do ##class(SourceControl.Git.Change).RefreshUncommitted()
160+
if '$listfind(readOnlyCommands,baseCommand) {
161+
do ##class(SourceControl.Git.Change).RefreshUncommitted()
162+
}
158163
set handled = 1
159164
}
160165
}
@@ -218,4 +223,3 @@ ClassMethod GetSettingsURL(%request As %CSP.Request) As %SystemBase
218223
}
219224

220225
}
221-

csp/webuidriver.csp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
} catch e {
2020
// ignore; may occur on platform versions without the above properties
2121
}
22+
do %session.Unlock()
2223

2324
// Serve static content when appropriate.
2425
// index.html

0 commit comments

Comments
 (0)