ttsc 0.32.1 → 0.33.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (111) hide show
  1. package/cmd/ttsc-cache/account.go +94 -0
  2. package/cmd/ttsc-cache/interruption_test.go +176 -0
  3. package/cmd/ttsc-cache/main.go +252 -0
  4. package/cmd/ttsc-cache/merge.go +324 -0
  5. package/cmd/ttsc-cache/merge_test.go +161 -0
  6. package/cmd/ttsc-cache/prepare.go +330 -0
  7. package/cmd/ttsc-cache/prepare_test.go +456 -0
  8. package/cmd/ttsc-cache/protocol_test.go +119 -0
  9. package/cmd/ttsc-cache/publisher_seal_test.go +135 -0
  10. package/cmd/ttsc-cache/seal.go +427 -0
  11. package/cmd/ttsc-cache/stock_go_failure_test.go +94 -0
  12. package/cmd/ttsc-cache/stock_go_test.go +163 -0
  13. package/cmd/ttsc-cache/store.go +249 -0
  14. package/cmd/ttsc-cache/store_test.go +104 -0
  15. package/cmd/ttsc-cache/tuple.go +107 -0
  16. package/cmd/ttsc-cache/tuple_test.go +62 -0
  17. package/cmd/ttsc-cache/usage.go +73 -0
  18. package/driver/configloaderscratch/canonical_directory_other.go +34 -0
  19. package/driver/configloaderscratch/canonical_directory_windows.go +60 -0
  20. package/driver/configloaderscratch/identity_temp_base.go +81 -0
  21. package/driver/configloaderscratch/identity_temp_base_posix_test.go +124 -0
  22. package/driver/configloaderscratch/identity_temp_base_windows_test.go +156 -0
  23. package/driver/emit_provenance.go +7 -5
  24. package/driver/resolutioninputs/recorder.cjs +16 -4
  25. package/driver/rewrite.go +25 -16
  26. package/driver/write_file_if_changed.go +69 -0
  27. package/driver/write_file_if_changed_test.go +148 -0
  28. package/internal/packageartifacts/manifest.go +288 -0
  29. package/internal/packageartifacts/manifest_test.go +127 -0
  30. package/lib/compiler/internal/build/BuildExecution.d.ts +11 -7
  31. package/lib/compiler/internal/build/BuildExecution.js +22 -8
  32. package/lib/compiler/internal/build/BuildExecution.js.map +1 -1
  33. package/lib/compiler/internal/build/ResidentCheckWatchSession.d.ts +10 -10
  34. package/lib/compiler/internal/build/ResidentCheckWatchSession.js +19 -14
  35. package/lib/compiler/internal/build/ResidentCheckWatchSession.js.map +1 -1
  36. package/lib/compiler/internal/build/RunBuildOptions.d.ts +8 -1
  37. package/lib/compiler/internal/build/TsgoArguments.d.ts +16 -0
  38. package/lib/compiler/internal/build/TsgoArguments.js +19 -0
  39. package/lib/compiler/internal/build/TsgoArguments.js.map +1 -1
  40. package/lib/compiler/internal/captureProcessOutput.d.ts +16 -13
  41. package/lib/compiler/internal/captureProcessOutput.js +22 -14
  42. package/lib/compiler/internal/captureProcessOutput.js.map +1 -1
  43. package/lib/compiler/internal/startResidentTransform.js +3 -1
  44. package/lib/compiler/internal/startResidentTransform.js.map +1 -1
  45. package/lib/compiler/internal/transformProjectInMemory.js +5 -1
  46. package/lib/compiler/internal/transformProjectInMemory.js.map +1 -1
  47. package/lib/launcher/internal/runtime/PluginDescriptorInputReport.d.ts +43 -0
  48. package/lib/launcher/internal/runtime/PluginDescriptorInputReport.js +106 -0
  49. package/lib/launcher/internal/runtime/PluginDescriptorInputReport.js.map +1 -0
  50. package/lib/launcher/internal/runtime/installRuntimeHooks.js +15 -29
  51. package/lib/launcher/internal/runtime/installRuntimeHooks.js.map +1 -1
  52. package/lib/launcher/internal/ttscserver/runTtscserver.js +1 -0
  53. package/lib/launcher/internal/ttscserver/runTtscserver.js.map +1 -1
  54. package/lib/launcher/internal/watch/WatchTopology.js +5 -4
  55. package/lib/launcher/internal/watch/WatchTopology.js.map +1 -1
  56. package/lib/plugin/internal/CapabilityResolutionFormat.js +4 -3
  57. package/lib/plugin/internal/CapabilityResolutionFormat.js.map +1 -1
  58. package/lib/plugin/internal/load/COMMONJS_PLUGIN_DESCRIPTOR_SHIM_SOURCE.js +6 -0
  59. package/lib/plugin/internal/load/COMMONJS_PLUGIN_DESCRIPTOR_SHIM_SOURCE.js.map +1 -1
  60. package/lib/plugin/internal/load/PLUGIN_DESCRIPTOR_SHIM_SOURCE.js +6 -0
  61. package/lib/plugin/internal/load/PLUGIN_DESCRIPTOR_SHIM_SOURCE.js.map +1 -1
  62. package/lib/plugin/internal/load/PluginDescriptorEvaluationCache.js +3 -3
  63. package/lib/plugin/internal/load/PluginDescriptorEvaluationCache.js.map +1 -1
  64. package/lib/plugin/internal/load/descriptorRecordEnvironment.d.ts +15 -0
  65. package/lib/plugin/internal/load/descriptorRecordEnvironment.js +24 -0
  66. package/lib/plugin/internal/load/descriptorRecordEnvironment.js.map +1 -0
  67. package/lib/plugin/internal/load/loadProjectPlugins.js +28 -26
  68. package/lib/plugin/internal/load/loadProjectPlugins.js.map +1 -1
  69. package/lib/plugin/internal/source/SourceBuildCacheLayout.d.ts +2 -0
  70. package/lib/plugin/internal/source/SourceBuildCacheLayout.js +2 -0
  71. package/lib/plugin/internal/source/SourceBuildCacheLayout.js.map +1 -1
  72. package/lib/plugin/internal/source/SourcePackageArtifacts.d.ts +145 -0
  73. package/lib/plugin/internal/source/SourcePackageArtifacts.js +612 -0
  74. package/lib/plugin/internal/source/SourcePackageArtifacts.js.map +1 -0
  75. package/lib/plugin/internal/source/buildSourcePlugin.js +54 -4
  76. package/lib/plugin/internal/source/buildSourcePlugin.js.map +1 -1
  77. package/lib/plugin/internal/source/prunePackageArtifactRoot.d.ts +19 -0
  78. package/lib/plugin/internal/source/prunePackageArtifactRoot.js +95 -0
  79. package/lib/plugin/internal/source/prunePackageArtifactRoot.js.map +1 -0
  80. package/lib/plugin/internal/source/resolveCleanTargets.js +1 -1
  81. package/lib/plugin/internal/source/resolveCleanTargets.js.map +1 -1
  82. package/lib/plugin/internal/source/resolveSourceBuildCachePaths.js +9 -4
  83. package/lib/plugin/internal/source/resolveSourceBuildCachePaths.js.map +1 -1
  84. package/lib/plugin/internal/source/selectedRecordIdentityTempParent.d.ts +18 -0
  85. package/lib/plugin/internal/source/selectedRecordIdentityTempParent.js +46 -0
  86. package/lib/plugin/internal/source/selectedRecordIdentityTempParent.js.map +1 -0
  87. package/package.json +8 -8
  88. package/src/compiler/internal/build/BuildExecution.ts +30 -10
  89. package/src/compiler/internal/build/ResidentCheckWatchSession.ts +23 -22
  90. package/src/compiler/internal/build/RunBuildOptions.ts +9 -1
  91. package/src/compiler/internal/build/TsgoArguments.ts +22 -0
  92. package/src/compiler/internal/captureProcessOutput.ts +28 -14
  93. package/src/compiler/internal/startResidentTransform.ts +7 -1
  94. package/src/compiler/internal/transformProjectInMemory.ts +9 -1
  95. package/src/launcher/internal/runtime/PluginDescriptorInputReport.ts +100 -0
  96. package/src/launcher/internal/runtime/installRuntimeHooks.ts +57 -55
  97. package/src/launcher/internal/ttscserver/runTtscserver.ts +5 -0
  98. package/src/launcher/internal/watch/WatchTopology.ts +5 -4
  99. package/src/plugin/internal/CapabilityResolutionFormat.ts +4 -3
  100. package/src/plugin/internal/load/COMMONJS_PLUGIN_DESCRIPTOR_SHIM_SOURCE.ts +4 -0
  101. package/src/plugin/internal/load/PLUGIN_DESCRIPTOR_SHIM_SOURCE.ts +4 -0
  102. package/src/plugin/internal/load/PluginDescriptorEvaluationCache.ts +3 -3
  103. package/src/plugin/internal/load/descriptorRecordEnvironment.ts +24 -0
  104. package/src/plugin/internal/load/loadProjectPlugins.ts +45 -29
  105. package/src/plugin/internal/source/SourceBuildCacheLayout.ts +3 -0
  106. package/src/plugin/internal/source/SourcePackageArtifacts.ts +787 -0
  107. package/src/plugin/internal/source/buildSourcePlugin.ts +82 -12
  108. package/src/plugin/internal/source/prunePackageArtifactRoot.ts +100 -0
  109. package/src/plugin/internal/source/resolveCleanTargets.ts +1 -0
  110. package/src/plugin/internal/source/resolveSourceBuildCachePaths.ts +11 -4
  111. package/src/plugin/internal/source/selectedRecordIdentityTempParent.ts +40 -0
@@ -0,0 +1,330 @@
1
+ package main
2
+
3
+ import (
4
+ "archive/zip"
5
+ "context"
6
+ "crypto/sha256"
7
+ "encoding/hex"
8
+ "errors"
9
+ "fmt"
10
+ "io"
11
+ "net"
12
+ "net/http"
13
+ "net/url"
14
+ "os"
15
+ "path/filepath"
16
+ "strings"
17
+ "time"
18
+
19
+ "github.com/samchon/ttsc/packages/ttsc/internal/packageartifacts"
20
+ )
21
+
22
+ type preparationReceipt struct {
23
+ SelectedPacks []string `json:"selectedPacks"`
24
+ DownloadedPacks int `json:"downloadedPacks"`
25
+ Requests int `json:"requests"`
26
+ CompressedBytes int64 `json:"compressedBytes"`
27
+ DecodedBytes int64 `json:"decodedBytes"`
28
+ Storage artifactAccounting `json:"storage"`
29
+ DownloadMillis int64 `json:"downloadMillis"`
30
+ ExtractMillis int64 `json:"extractMillis"`
31
+ AdmissionMillis int64 `json:"admissionMillis"`
32
+ TotalMillis int64 `json:"totalMillis"`
33
+ }
34
+
35
+ const maxSelectedCompressedBytes int64 = 512 << 20
36
+
37
+ const maxSelectedDecodedBytes int64 = 4 << 30
38
+
39
+ const maxRetainedBaseBytes int64 = 8 << 30
40
+
41
+ const maxRetainedStagingBytes int64 = 5 << 30
42
+
43
+ func preparePacks(ctx context.Context, store *artifactStore, manifest packageartifacts.Manifest, names []string) (preparationReceipt, error) {
44
+ started := time.Now()
45
+ var receipt preparationReceipt
46
+ if len(names) == 0 || len(names) > 8 {
47
+ return receipt, errors.New("select between one and eight artifact packs")
48
+ }
49
+ packs := make(map[string]packageartifacts.Pack, len(manifest.Packs))
50
+ for _, pack := range manifest.Packs {
51
+ packs[pack.Name] = pack
52
+ }
53
+ seen := make(map[string]bool, len(names))
54
+ selected := make([]packageartifacts.Pack, 0, len(names))
55
+ var compressed, decoded int64
56
+ for _, name := range names {
57
+ pack, exists := packs[name]
58
+ if !exists || seen[name] {
59
+ return receipt, fmt.Errorf("unknown or repeated artifact pack %q", name)
60
+ }
61
+ seen[name] = true
62
+ receipt.SelectedPacks = append(receipt.SelectedPacks, name)
63
+ if pack.Length > maxSelectedCompressedBytes-compressed || pack.DecodedBytes > maxSelectedDecodedBytes-decoded {
64
+ return receipt, errors.New("selected artifact packs exceed transfer or decoded byte policy")
65
+ }
66
+ compressed += pack.Length
67
+ decoded += pack.DecodedBytes
68
+ selected = append(selected, pack)
69
+ }
70
+ for _, pack := range selected {
71
+ if admittedPack(store, pack, manifest.Actions) {
72
+ continue
73
+ }
74
+ accounting, err := accountArtifactStore(store)
75
+ if err != nil {
76
+ return receipt, fmt.Errorf("accounting for artifact admission: %w", err)
77
+ }
78
+ if accounting.BaseBytes > maxRetainedBaseBytes-pack.DecodedBytes || accounting.StagingBytes > maxRetainedStagingBytes-pack.DecodedBytes-pack.Length {
79
+ return receipt, errors.New("artifact storage exceeds preparation byte policy")
80
+ }
81
+ downloaded, err := prepareSelectedPack(ctx, store, pack, manifest.Actions, &receipt)
82
+ if err != nil {
83
+ return receipt, err
84
+ }
85
+ if downloaded {
86
+ receipt.DownloadedPacks++
87
+ }
88
+ }
89
+ var err error
90
+ receipt.Storage, err = accountArtifactStore(store)
91
+ if err != nil {
92
+ return receipt, fmt.Errorf("accounting for prepared artifacts: %w", err)
93
+ }
94
+ receipt.TotalMillis = time.Since(started).Milliseconds()
95
+ return receipt, nil
96
+ }
97
+
98
+ func prepareSelectedPack(ctx context.Context, store *artifactStore, pack packageartifacts.Pack, actions []packageartifacts.Action, receipt *preparationReceipt) (bool, error) {
99
+ lock := filepath.ToSlash(filepath.Join("base", "staging", pack.Name+"-"+pack.SHA256+".lock"))
100
+ deadline := time.Now().Add(120 * time.Second)
101
+ for {
102
+ if err := ctx.Err(); err != nil {
103
+ return false, err
104
+ }
105
+ file, err := store.root.OpenFile(lock, os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0600)
106
+ if err == nil {
107
+ defer store.root.Remove(lock)
108
+ defer file.Close()
109
+ if admittedPack(store, pack, actions) {
110
+ return false, nil
111
+ }
112
+ if err := downloadAndAdmitPack(ctx, store, pack, actions, receipt); err != nil {
113
+ return false, err
114
+ }
115
+ return true, nil
116
+ }
117
+ if !errors.Is(err, os.ErrExist) {
118
+ return false, err
119
+ }
120
+ info, err := store.root.Lstat(lock)
121
+ if err != nil && !errors.Is(err, os.ErrNotExist) {
122
+ return false, err
123
+ }
124
+ if err == nil && !info.Mode().IsRegular() {
125
+ return false, errors.New("artifact preparation lock is not an ordinary file")
126
+ }
127
+ if admittedPack(store, pack, actions) {
128
+ return false, nil
129
+ }
130
+ if time.Now().After(deadline) {
131
+ return false, fmt.Errorf("artifact preparation lock for %s did not settle", pack.Name)
132
+ }
133
+ select {
134
+ case <-ctx.Done():
135
+ return false, ctx.Err()
136
+ case <-time.After(50 * time.Millisecond):
137
+ }
138
+ }
139
+ }
140
+
141
+ func admittedPack(store *artifactStore, pack packageartifacts.Pack, actions []packageartifacts.Action) bool {
142
+ name := filepath.ToSlash(filepath.Join("base", "packs", pack.Name+"-"+pack.SHA256))
143
+ info, err := store.root.Lstat(name)
144
+ if err != nil || !info.IsDir() || info.Mode()&os.ModeSymlink != 0 {
145
+ return false
146
+ }
147
+ receipt, err := store.root.ReadFile(filepath.ToSlash(filepath.Join(name, ".complete")))
148
+ if err != nil || string(receipt) != pack.SHA256+"\n" {
149
+ return false
150
+ }
151
+ seen := make(map[string]bool)
152
+ for _, action := range actions {
153
+ if action.Pack != pack.Name || seen[action.OutputID] {
154
+ continue
155
+ }
156
+ seen[action.OutputID] = true
157
+ body, err := store.root.Lstat(filepath.ToSlash(filepath.Join(name, "bodies", action.OutputID)))
158
+ if err != nil || !body.Mode().IsRegular() || body.Size() != action.BodyLength {
159
+ return false
160
+ }
161
+ }
162
+ return len(seen) == pack.FileCount
163
+ }
164
+
165
+ func downloadAndAdmitPack(ctx context.Context, store *artifactStore, pack packageartifacts.Pack, actions []packageartifacts.Action, receipt *preparationReceipt) error {
166
+ stage := filepath.ToSlash(filepath.Join("base", "staging", randomName()))
167
+ if err := store.root.Mkdir(stage, 0700); err != nil {
168
+ return err
169
+ }
170
+ defer store.root.RemoveAll(stage)
171
+ if err := store.root.Mkdir(filepath.ToSlash(filepath.Join(stage, "bodies")), 0700); err != nil {
172
+ return err
173
+ }
174
+ archive := filepath.ToSlash(filepath.Join(stage, "pack.zip"))
175
+ started := time.Now()
176
+ if err := downloadPack(ctx, store, pack, archive, receipt); err != nil {
177
+ return err
178
+ }
179
+ receipt.DownloadMillis += time.Since(started).Milliseconds()
180
+ started = time.Now()
181
+ if err := extractPack(store, pack, archive, stage, actions, receipt); err != nil {
182
+ return err
183
+ }
184
+ receipt.ExtractMillis += time.Since(started).Milliseconds()
185
+ started = time.Now()
186
+ if err := store.root.Remove(archive); err != nil {
187
+ return err
188
+ }
189
+ marker := filepath.ToSlash(filepath.Join(stage, ".complete"))
190
+ file, err := store.root.OpenFile(marker, os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0600)
191
+ if err != nil {
192
+ return err
193
+ }
194
+ _, writeErr := io.WriteString(file, pack.SHA256+"\n")
195
+ syncErr := file.Sync()
196
+ closeErr := file.Close()
197
+ if writeErr != nil || syncErr != nil || closeErr != nil {
198
+ return errors.Join(writeErr, syncErr, closeErr)
199
+ }
200
+ final := filepath.ToSlash(filepath.Join("base", "packs", pack.Name+"-"+pack.SHA256))
201
+ if err := store.root.Rename(stage, final); err != nil {
202
+ if admittedPack(store, pack, actions) {
203
+ return nil
204
+ }
205
+ return fmt.Errorf("admitting artifact pack %s: %w", pack.Name, err)
206
+ }
207
+ receipt.AdmissionMillis += time.Since(started).Milliseconds()
208
+ return nil
209
+ }
210
+
211
+ func downloadPack(ctx context.Context, store *artifactStore, pack packageartifacts.Pack, destination string, receipt *preparationReceipt) error {
212
+ client := &http.Client{
213
+ Timeout: 120 * time.Second,
214
+ CheckRedirect: func(req *http.Request, via []*http.Request) error {
215
+ if len(via) >= 5 || !safeRedirect(req.URL) {
216
+ return errors.New("artifact redirect policy rejected destination")
217
+ }
218
+ receipt.Requests++
219
+ return nil
220
+ },
221
+ }
222
+ request, err := http.NewRequestWithContext(ctx, http.MethodGet, pack.URL, nil)
223
+ if err != nil {
224
+ return fmt.Errorf("invalid artifact pack URL for %s", pack.Name)
225
+ }
226
+ receipt.Requests++
227
+ response, err := client.Do(request)
228
+ if err != nil {
229
+ return fmt.Errorf("artifact pack %s transfer failed (%T)", pack.Name, err)
230
+ }
231
+ defer response.Body.Close()
232
+ if response.StatusCode != http.StatusOK {
233
+ return fmt.Errorf("artifact pack %s returned HTTP %d", pack.Name, response.StatusCode)
234
+ }
235
+ if response.ContentLength > pack.Length {
236
+ return fmt.Errorf("artifact pack %s exceeds compressed byte limit", pack.Name)
237
+ }
238
+ file, err := store.root.OpenFile(destination, os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0600)
239
+ if err != nil {
240
+ return err
241
+ }
242
+ hash := sha256.New()
243
+ count, copyErr := io.Copy(io.MultiWriter(file, hash), io.LimitReader(response.Body, pack.Length+1))
244
+ syncErr := file.Sync()
245
+ closeErr := file.Close()
246
+ if copyErr != nil || syncErr != nil || closeErr != nil {
247
+ return errors.Join(copyErr, syncErr, closeErr)
248
+ }
249
+ receipt.CompressedBytes += count
250
+ if count != pack.Length || hex.EncodeToString(hash.Sum(nil)) != pack.SHA256 {
251
+ return fmt.Errorf("artifact pack %s has wrong compressed length or digest", pack.Name)
252
+ }
253
+ return nil
254
+ }
255
+
256
+ func extractPack(store *artifactStore, pack packageartifacts.Pack, archive, stage string, actions []packageartifacts.Action, receipt *preparationReceipt) error {
257
+ file, err := store.root.Open(archive)
258
+ if err != nil {
259
+ return err
260
+ }
261
+ defer file.Close()
262
+ info, err := file.Stat()
263
+ if err != nil || !info.Mode().IsRegular() || info.Size() != pack.Length {
264
+ return fmt.Errorf("artifact pack %s changed before extraction", pack.Name)
265
+ }
266
+ reader, err := zip.NewReader(file, info.Size())
267
+ if err != nil {
268
+ return fmt.Errorf("artifact pack %s is not a ZIP: %w", pack.Name, err)
269
+ }
270
+ expected := make(map[string]int64)
271
+ for _, action := range actions {
272
+ if action.Pack == pack.Name {
273
+ expected[action.OutputID] = action.BodyLength
274
+ }
275
+ }
276
+ if len(reader.File) != pack.FileCount || len(expected) != pack.FileCount {
277
+ return fmt.Errorf("artifact pack %s has wrong file count", pack.Name)
278
+ }
279
+ var total int64
280
+ seen := make(map[string]bool, len(expected))
281
+ for _, entry := range reader.File {
282
+ if !strings.HasPrefix(entry.Name, "bodies/") {
283
+ return fmt.Errorf("artifact pack %s contains an unexpected path", pack.Name)
284
+ }
285
+ outputID := strings.TrimPrefix(entry.Name, "bodies/")
286
+ length, exists := expected[outputID]
287
+ if !exists || seen[outputID] || entry.UncompressedSize64 != uint64(length) || total > pack.DecodedBytes-length {
288
+ return fmt.Errorf("artifact pack %s contains an unexpected body", pack.Name)
289
+ }
290
+ seen[outputID] = true
291
+ compressed, err := entry.Open()
292
+ if err != nil {
293
+ return err
294
+ }
295
+ destination := filepath.ToSlash(filepath.Join(stage, "bodies", outputID))
296
+ file, err := store.root.OpenFile(destination, os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0600)
297
+ if err != nil {
298
+ compressed.Close()
299
+ return err
300
+ }
301
+ hash := sha256.New()
302
+ count, copyErr := io.Copy(io.MultiWriter(file, hash), io.LimitReader(compressed, length+1))
303
+ closeInputErr := compressed.Close()
304
+ syncErr := file.Sync()
305
+ closeOutputErr := file.Close()
306
+ if copyErr != nil || closeInputErr != nil || syncErr != nil || closeOutputErr != nil {
307
+ return errors.Join(copyErr, closeInputErr, syncErr, closeOutputErr)
308
+ }
309
+ if count != length || hex.EncodeToString(hash.Sum(nil)) != outputID {
310
+ return fmt.Errorf("artifact pack %s contains a corrupt body", pack.Name)
311
+ }
312
+ total += count
313
+ }
314
+ if total != pack.DecodedBytes {
315
+ return fmt.Errorf("artifact pack %s has wrong decoded byte count", pack.Name)
316
+ }
317
+ receipt.DecodedBytes += total
318
+ return nil
319
+ }
320
+
321
+ func safeRedirect(target *url.URL) bool {
322
+ if target.User != nil || target.Fragment != "" {
323
+ return false
324
+ }
325
+ if target.Scheme == "https" {
326
+ return true
327
+ }
328
+ address := net.ParseIP(target.Hostname())
329
+ return target.Scheme == "http" && address != nil && address.IsLoopback()
330
+ }