96 lines
6.0 KiB
Markdown
96 lines
6.0 KiB
Markdown
# Piano di Implementazione: Single-Thread Communication Proxy (Fanuc)
|
|
|
|
## 1. Obiettivo
|
|
Garantire che tutte le interazioni con l'oggetto `FANUC_ref` avvengano esclusivamente sul
|
|
thread che ha effettuato l'inizializzazione della connessione, eliminando i crash e le
|
|
instabilità causate dalla mancanza di thread-safety del driver nativo, quando il parametro
|
|
`MachWLoopSingleThread` è impostato su `true`.
|
|
|
|
## 2. Diagnosi del problema (perché il pattern proxy è la soluzione)
|
|
`FANUC_ref` viene toccato da almeno 3 thread diversi:
|
|
- il thread di init/UI (costruttore `Fanuc` → `Runtime.CreateNC` + `tryConnect`);
|
|
- il thread macchina dedicato `WorkerLoopMachine` (`AdapterForm.cs:2542`) → `doMachineTask`
|
|
(`Generic.cs:723`) → metodi `process*` override di `Fanuc` che chiamano `FANUC_ref` direttamente;
|
|
- il thread worker server (`Task.Run` pool) → `doServerTaskAsync` (`AdapterForm.cs:1556/1713`)
|
|
e `tryConnect` (`2498`).
|
|
|
|
Il `_plcLock` (SemaphoreSlim) in `AdapterForm` serializza solo i **tempi** di esecuzione tra
|
|
macchina e server, **non l'affinità di thread**: chi vince il lock esegue su thread diversi nel
|
|
tempo. Il driver FOCAS richiede tutte le chiamate sullo stesso thread → crash atteso. Il check
|
|
`validThread` in `WorkerLoopMachine` (`2556`) verifica solo il loop macchina, non l'accesso a
|
|
`FANUC_ref`, quindi non basta.
|
|
|
|
**Soluzione**: marshalling di TUTTE le chiamate a `FANUC_ref` su un unico thread proprietario
|
|
(`_commThread`), che è anche il thread che crea la connessione NC.
|
|
|
|
## 3. Architettura Tecnica: Pattern Proxy
|
|
### Componenti del Proxy (interni alla classe `Fanuc`):
|
|
- **`_commQueue`**: `BlockingCollection<Action>` coda thread-safe delle azioni da eseguire.
|
|
- **`_commThread`**: thread unico e persistente, avviato durante l'inizializzazione. Unico
|
|
proprietario di `FANUC_ref` (incluso `Runtime.CreateNC`).
|
|
- **`ExecuteProxy<T>(Func<T>)`**: marshalling sincrono per il chiamante (blocca su
|
|
`tcs.Task.Result`), asincrono rispetto al thread del proxy.
|
|
|
|
### Strategia di marshalling (LEAF, non body)
|
|
Ogni accesso diretto a `FANUC_ref.X(...)` viene sostituito con una chiamata a `ExecuteProxy`.
|
|
Questo garantisce l'affinità di thread per ogni singola operazione FOCAS e **non introduce
|
|
reentrancy** (le lambda sul comm-thread chiamano solo `FANUC_ref` direttamente, mai metodi
|
|
proxied), quindi **niente deadlock da rientro**.
|
|
|
|
### Gestione dei parametri `ref`/`out`
|
|
I thread non possono catturare `ref`/`out` di un **parametro di metodo** in una lambda. Per i
|
|
metodi wrapper pubblici con `ref` parametro servono **helper dedicati con copy-in/copy-out**:
|
|
copiano il valore in una variabile locale, marshallano la chiamata che scrive la locale, e al
|
|
ritorno riscrivono la locale nel parametro. Per i `ref` a **variabili locali** (che una lambda
|
|
può catturare) si usa il marshalling inline.
|
|
|
|
## 4. Modifiche ai Componenti (dettaglio)
|
|
|
|
### A. Infrastruttura (interna a `Fanuc.cs`)
|
|
1. `_commQueue` (`BlockingCollection<Action>`) + `_commThread` (campo).
|
|
2. `StartCommThread()`: crea/avvia il thread dedicato se non è vivo; crea una coda nuova se
|
|
serve (riavvio). Il thread drena la coda in loop, inghiottendo le eccezioni delle singole
|
|
azioni per non morire.
|
|
3. `StopCommThread()`: `CompleteAdding()` + `Join(timeout)` + `_commThread = null`.
|
|
4. `ExecuteProxy<T>(Func<T>)`:
|
|
- se `MachWLoopSingleThread && _commThread != null && _commThread.IsAlive`: accoda l'azione e
|
|
ritorna `tcs.Task.Result` (propaga le eccezioni al chiamante);
|
|
- altrimenti (thread non attivo / flag off): esegue inline sotto `lock(_lockSync)` come
|
|
fallback anti-hang (durante lo stop nessuna chiamata deve arrivare comunque).
|
|
|
|
### B. Lifecycle del thread (CORRETTO rispetto alla versione precedente)
|
|
Il thread dedicato deve essere **lo stesso che crea FANUC_ref** (affinità FOCAS). Non va mai
|
|
fermato/riavviato a metà vita dell'oggetto, altrimenti il nuovo thread ≠ thread creatore.
|
|
- **Costruttore**: `StartCommThread()` PRIMA di `Runtime.CreateNC`; la creazione NC +
|
|
assegnazione `FANUC_ref` vengono marshallate sul comm-thread.
|
|
- **`startAdapter` override**: se `_commThread` non è vivo → `StartCommThread()` + ricreazione
|
|
NC marshallata sul nuovo thread (copre il path stop→start sullo stesso oggetto senza
|
|
duplicare CreateNC sul path di avvio normale); poi `base.startAdapter`.
|
|
- **`stopAdapter` override**: `await base.stopAdapter(...)` (che marshalla la `Disconnect`) poi
|
|
`StopCommThread()` (nessun leak sul restart che ricrea l'oggetto).
|
|
|
|
### C. Rifattorizzazione dei metodi di I/O
|
|
Sostituzione delle chiamate dirette a `FANUC_ref` con il proxy:
|
|
- `FANUC_ref.Connected` → `ExecuteProxy(() => FANUC_ref.Connected)` (incluso dentro le stringhe
|
|
di log interpolate).
|
|
- `F_RW_*`, `F_Read_macro`, `getAllDynData`, `getSysInfo`, `getPrgNameMain`, `Is30Series` con
|
|
`ref` a **variabili locali** → `ExecuteProxy(() => FANUC_ref.X(..., ref locale))`.
|
|
- `Connect`/`Disconnect` con `ref` locale → `ExecuteProxy(() => { FANUC_ref.X(ref l); return true; })`.
|
|
- **4 helper copy-in/out** per i wrapper pubblici con `ref` **parametro**:
|
|
`ProxyMacroShort`, `ProxyMacroDouble` (per `FanucMemMacroRW`), `ProxyByte`, `ProxyByteArr`
|
|
(per `FanucMemRW` byte/byte[]).
|
|
|
|
## 5. Validazione e Test
|
|
- Log: tutte le chiamate a `FANUC_ref` devono presentare lo stesso `ManagedThreadId` del
|
|
comm-thread (FANUC-Comm-Thread), NON del WorkerLoopMachine.
|
|
- Test di connessione: `tryConnect` deve funzionare attraverso il proxy.
|
|
- Test stop→start e restart: verificare che `_commThread` venga ricreato con ricreazione NC e
|
|
che non ci siano double-create sull'avvio normale.
|
|
|
|
## 6. Note di sicurezza (pitfall)
|
|
- **Reentrancy**: con marshalling leaf non c'è rientro, ma `ExecuteProxy` resta difensivo.
|
|
- **Hang**: il fallback inline sotto lock evita `tcs.Task.Result` che si blocca se il thread è morto.
|
|
- **Shutdown**: `StopCommThread()` su `stopAdapter`; niente `CompleteAdding` durante il run.
|
|
- **`MachWLoopSingleThread`** è forzato a `true` nel costruttore (`Fanuc.cs:67`): il ramo proxy
|
|
è sempre attivo per FANUC; il ramo `else` con lock è un fallback di sicurezza.
|