Skip to content

Commit 84b1dcd

Browse files
committed
fix(git-import): memory issue when importing a long history
All imported versions were kept in memory. Now they can be garbage collected as soon as all child versions processed them.
1 parent d208987 commit 84b1dcd

File tree

2 files changed

+82
-36
lines changed

2 files changed

+82
-36
lines changed

mps-git-import/src/main/kotlin/org/modelix/mps/gitimport/DummyRepo.kt

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,9 @@ import org.jetbrains.mps.openapi.module.SRepositoryListener
1010
import org.jetbrains.mps.openapi.repository.CommandListener
1111
import org.jetbrains.mps.openapi.repository.ReadActionListener
1212
import org.jetbrains.mps.openapi.repository.WriteActionListener
13+
import java.lang.AutoCloseable
1314

14-
class DummyRepo : SRepository {
15+
class DummyRepo : SRepository, AutoCloseable {
1516
private val modelAccess = DummyModelAccess()
1617
private val registeredModules: MutableMap<SModuleId, SModule> = LinkedHashMap()
1718

@@ -35,6 +36,16 @@ class DummyRepo : SRepository {
3536
return null
3637
}
3738

39+
fun dispose() {
40+
for (entry in registeredModules) {
41+
(entry.value as AbstractModule).dispose()
42+
}
43+
}
44+
45+
override fun close() {
46+
dispose()
47+
}
48+
3849
override fun getModule(id: SModuleId): SModule? = registeredModules[id]
3950

4051
override fun getModules(): Iterable<SModule> = registeredModules.values

mps-git-import/src/main/kotlin/org/modelix/mps/gitimport/GitImporter.kt

Lines changed: 70 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,6 @@ import org.modelix.model.sync.bulk.ModelSynchronizer
3939
import org.modelix.model.sync.bulk.UnfilteredModelMask
4040
import org.modelix.mps.api.ModelixMpsApi
4141
import java.io.File
42-
import kotlin.invoke
4342
import kotlin.system.exitProcess
4443

4544
class GitImporter(
@@ -81,51 +80,71 @@ class GitImporter(
8180
val versionIndex = VersionIndex(initialRemoteVersion)
8281
var lastPushedVersion = initialRemoteVersion
8382

84-
val existingImports = HashMap<String, IVersion>()
83+
val importQueue = LinkedHashMap<String, PendingImport>()
84+
val fillQueue = DeepRecursiveFunction<ObjectId, PendingImport> { gitCommitId ->
85+
val gitCommit = git.repository.parseCommit(gitCommitId)
86+
importQueue[gitCommit.name]?.let { return@DeepRecursiveFunction it }
87+
88+
val existingVersionHash = versionIndex.findByGitCommitId(gitCommitId.name)
89+
val queueElement = if (existingVersionHash != null) {
90+
PendingImport(
91+
gitCommit = gitCommit,
92+
parentImports = null,
93+
importedVersion = null,
94+
existingVersionHash = existingVersionHash,
95+
)
96+
} else {
97+
PendingImport(
98+
gitCommit = gitCommit,
99+
parentImports = gitCommit.parents?.map { callRecursive(it) }.orEmpty(),
100+
importedVersion = null,
101+
existingVersionHash = null,
102+
)
103+
}
104+
importQueue[gitCommit.name] = queueElement
105+
queueElement
106+
}
107+
fillQueue(resolvedCommit)
85108

86-
val importVersion = DeepRecursiveFunction<ObjectId, IVersion> { gitCommitId ->
109+
println("Starting import")
110+
for (commitId in importQueue.keys.toList()) {
111+
val queueElement = importQueue.remove(commitId)!!
87112
try {
88-
val gitCommit = git.repository.parseCommit(gitCommitId)
89-
existingImports[gitCommit.name]?.let { return@DeepRecursiveFunction it }
90-
91-
val existingVersionHash = versionIndex.findByGitCommitId(gitCommitId.name)
92-
if (existingVersionHash != null) {
93-
return@DeepRecursiveFunction runBlocking {
94-
client.loadVersion(repositoryId, existingVersionHash.toString(), null).also { v ->
95-
(v as CLVersion).gitCommit?.let { c -> existingImports[c] = v }
96-
}
113+
when {
114+
queueElement.importedVersion != null -> continue
115+
queueElement.existingVersionHash != null -> {
116+
val version = client.loadVersion(repositoryId, queueElement.existingVersionHash!!.toString(), null)
117+
queueElement.success(version)
97118
}
98-
}
99-
100-
val parentImports = gitCommit.parents?.map { callRecursive(it) }.orEmpty()
101-
val mpsRepo = DummyRepo() // TODO dispose
102-
val importedVersion = ModelixMpsApi.runWithRepository(mpsRepo) {
103-
when (parentImports.size) {
104-
0 -> runImport(listOf(versionIndex.getInitialVersion()), gitCommit, git.repository, mpsRepo)
105-
1, 2 -> runImport(parentImports, gitCommit, git.repository, mpsRepo)
106-
else -> TODO()
119+
else -> {
120+
val parentImports = queueElement.parentImports.orEmpty().map { it.importedVersion!! }
121+
DummyRepo().use { mpsRepo ->
122+
val gitCommit = queueElement.gitCommit
123+
val importedVersion = ModelixMpsApi.runWithRepository(mpsRepo) {
124+
when (parentImports.size) {
125+
0 -> runImport(listOf(versionIndex.getInitialVersion()), gitCommit, git.repository, mpsRepo)
126+
1, 2 -> runImport(parentImports, gitCommit, git.repository, mpsRepo)
127+
else -> TODO()
128+
}
129+
}
130+
131+
runBlocking {
132+
println("Pushing $importedVersion")
133+
lastPushedVersion = client.push(targetBranch, importedVersion, importedVersion.getParentVersions(), force = true)
134+
}
135+
136+
queueElement.success(importedVersion)
137+
}
107138
}
108139
}
109-
110-
runBlocking {
111-
println("Pushing $importedVersion")
112-
lastPushedVersion = client.push(targetBranch, importedVersion, importedVersion.getParentVersions(), force = true)
113-
}
114-
115-
existingImports[gitCommit.name] = importedVersion
116-
117-
importedVersion
118140
} catch (ex: Exception) {
119-
println("Import failed for ${gitCommitId.name}")
141+
println("Import failed for ${queueElement.gitCommit.name}")
120142
ex.printStackTrace()
121143
throw ex
122144
}
123145
}
124146

125-
println("Starting import")
126-
val importedVersion = importVersion(resolvedCommit)
127-
println("Uploading new versions")
128-
client.push(targetBranch, importedVersion, lastPushedVersion, force = true)
147+
println("Import done")
129148
}
130149

131150
/**
@@ -270,6 +289,22 @@ class GitImporter(
270289
}
271290
}
272291
}
292+
293+
/**
294+
* The purpose of this class is to build a queue of to be imported versions and keep the result only as long as
295+
* necessary in memory. As soon as the result is consumed by all child imports, the result can be garbage collected.
296+
*/
297+
private class PendingImport(
298+
val gitCommit: RevCommit,
299+
var parentImports: List<PendingImport>?,
300+
var importedVersion: IVersion? = null,
301+
var existingVersionHash: ObjectHash? = null,
302+
) {
303+
fun success(version: IVersion) {
304+
importedVersion = version
305+
parentImports = null
306+
}
307+
}
273308
}
274309

275310
private fun IFile.findModuleFile(): IFile? {

0 commit comments

Comments
 (0)