Parallel CSV processing with worker pool
(ctx context.Context, uploadID, source string, csvReader *csv.Reader, columnMap map[string]int, store StoreFunc)
| 250 | |
| 251 | // Parallel CSV processing with worker pool |
| 252 | func processCSVRowsParallel(ctx context.Context, uploadID, source string, csvReader *csv.Reader, columnMap map[string]int, store StoreFunc) (int, error) { |
| 253 | workerCount := runtime.NumCPU() |
| 254 | if workerCount > DefaultWorkerCount { |
| 255 | workerCount = DefaultWorkerCount |
| 256 | } |
| 257 | |
| 258 | rowChan := make(chan []string, DefaultChannelSize) |
| 259 | errChan := make(chan error, workerCount) |
| 260 | var wg sync.WaitGroup |
| 261 | totalCount := int64(0) |
| 262 | var countMu sync.Mutex |
| 263 | |
| 264 | // Start workers |
| 265 | for i := 0; i < workerCount; i++ { |
| 266 | wg.Add(1) |
| 267 | go func() { |
| 268 | defer wg.Done() |
| 269 | processWorker(ctx, uploadID, source, rowChan, columnMap, store, &totalCount, &countMu, errChan) |
| 270 | }() |
| 271 | } |
| 272 | |
| 273 | // Read CSV rows and send to workers |
| 274 | go func() { |
| 275 | rowNum := 1 |
| 276 | for { |
| 277 | record, err := csvReader.Read() |
| 278 | if err == io.EOF { |
| 279 | break |
| 280 | } |
| 281 | if err != nil { |
| 282 | errChan <- fmt.Errorf("error reading row %d: %w", rowNum, err) |
| 283 | continue |
| 284 | } |
| 285 | |
| 286 | rowNum++ |
| 287 | |
| 288 | // Make a copy since csvReader.ReuseRecord is true |
| 289 | recordCopy := make([]string, len(record)) |
| 290 | copy(recordCopy, record) |
| 291 | |
| 292 | select { |
| 293 | case <-ctx.Done(): |
| 294 | close(rowChan) |
| 295 | return |
| 296 | case rowChan <- recordCopy: |
| 297 | } |
| 298 | } |
| 299 | close(rowChan) |
| 300 | }() |
| 301 | |
| 302 | // Wait for workers to finish |
| 303 | wg.Wait() |
| 304 | close(errChan) |
| 305 | |
| 306 | // Collect errors |
| 307 | var errs []error |
| 308 | for err := range errChan { |
| 309 | errs = append(errs, err) |
no test coverage detected