diff --git a/models/GpsTracksModels.js b/models/GpsTracksModels.js index 74f5f0f..50f0009 100755 --- a/models/GpsTracksModels.js +++ b/models/GpsTracksModels.js @@ -393,143 +393,153 @@ class GpsTracksModels { static bundleCreate2(logs = {}, rltm = {}) { return new Promise(async (resolve, reject) => { - let lock = null - try { - const conn = await MysqlHelpers.createConnection() - await MysqlHelpers.createTrx(conn) + let lock = null; + let trackLock = null; // Scope dipindah ke atas agar bisa di-unlock di akhir + let conn = null; // Scope dipindah ke atas agar bisa di-rollback - let rltmLength = Object.keys(rltm).length - let resLogs = undefined + try { + conn = await MysqlHelpers.createConnection(); + await MysqlHelpers.createTrx(conn); + + let rltmLength = Object.keys(rltm).length; + let resLogs = undefined; if (Object.keys(logs).length > 0) { resLogs = await MysqlHelpers.queryTrx( conn, `INSERT INTO t_gps_tracks SET ?;`, - [logs], - ) + [logs] + ); - let trackLock = null + if (logs.action == "location") { + // Lock 1: Track Insert + trackLock = await rltmMutex.lock( + `tracks_insert:${logs.device_id}`, + { + duration: 9000, // Diubah menjadi 9 detik (9000 ms) + maxWait: 9000, + } + ); - try { - if (logs.action == "location") { + const date = logs.crt_d; + const mm = moment.unix(date).format("MM"); + const yy = moment.unix(date).format("YY"); - trackLock = await rltmMutex.lock( - `tracks_insert:${logs.device_id}`, - { - duration: 90000, - maxWait: 90000, - } - ) + logs.id = resLogs.insertId; - const date = logs.crt_d - const mm = moment.unix(date).format("MM") - const yy = moment.unix(date).format("YY") - - logs.id = resLogs.insertId - - await MysqlHelpers.queryTrx( - conn, - ` - INSERT INTO tracks_${yy}${mm} - SET ? - `, - [logs] - ) - } - } finally { - if (trackLock) { - await rltmMutex.unlock(trackLock) - } + await MysqlHelpers.queryTrx( + conn, + `INSERT INTO tracks_${yy}${mm} SET ?`, + [logs] + ); } + // HAPUS blok finally di sini. trackLock WAJIB ditahan sampai COMMIT selesai. } - if (rltmLength > 0 && typeof resLogs !== "undefined") - rltm.master_id = resLogs.insertId + if (rltmLength > 0 && typeof resLogs !== "undefined") { + rltm.master_id = resLogs.insertId; + } - if ( - rltmLength > 0 && - rltm.latitude !== null && - rltm.longitude !== null - ) { - // === KUNCI DI SINI === - // Lock per device_id supaya SELECT-DELETE-INSERT untuk - // device yang sama tidak berjalan bersamaan dari request/worker - // lain. Inilah yang tadinya memicu ER_DUP_ENTRY dan - // "Lock wait timeout" karena banyak transaksi menumpuk - // menunggu baris yang sama. + if (rltmLength > 0 && rltm.latitude !== null && rltm.longitude !== null) { + // Lock 2: Realtime Update lock = await rltmMutex.lock(`t_gps_tracks_rltm:${rltm.device_id}`, { - duration: 90000, // maksimal lock dipegang 5 detik - maxWait: 90000, // menunggu lock maksimal 10 detik lalu error - }) + duration: 9000, // Diubah menjadi 9 detik (9000 ms) + maxWait: 9000, + }); let devices = await MysqlHelpers.queryTrx( conn, - `SELECT id FROM t_gps_tracks_rltm as rltm WHERE rltm.device_id = ?`, - [rltm.device_id], - ) - if (devices.length > 1) + `SELECT id FROM t_gps_tracks_rltm WHERE device_id = ?`, + [rltm.device_id] + ); + if (devices.length > 1) { await MysqlHelpers.queryTrx( conn, - `DELETE from t_gps_tracks_rltm WHERE device_id = ?;`, - [rltm.device_id], - ) + `DELETE FROM t_gps_tracks_rltm WHERE device_id = ?;`, + [rltm.device_id] + ); + } if (rltm.vhc_id != 0) { let vhcs = await MysqlHelpers.queryTrx( conn, - `SELECT id FROM t_gps_tracks_rltm as rltm WHERE rltm.vhc_id = ?`, - [rltm.vhc_id], - ) - if (vhcs.length > 1) + `SELECT id FROM t_gps_tracks_rltm WHERE vhc_id = ?`, + [rltm.vhc_id] + ); + if (vhcs.length > 1) { await MysqlHelpers.queryTrx( conn, - `DELETE from t_gps_tracks_rltm WHERE vhc_id = ?;`, - [rltm.vhc_id], - ) + `DELETE FROM t_gps_tracks_rltm WHERE vhc_id = ?;`, + [rltm.vhc_id] + ); + } } if (rltm.drv_id != 0) { let drvs = await MysqlHelpers.queryTrx( conn, - `SELECT id FROM t_gps_tracks_rltm as rltm WHERE rltm.drv_id = ?`, - [rltm.drv_id], - ) - if (drvs.length > 1) + `SELECT id FROM t_gps_tracks_rltm WHERE drv_id = ?`, + [rltm.drv_id] + ); + if (drvs.length > 1) { await MysqlHelpers.queryTrx( conn, - `DELETE from t_gps_tracks_rltm WHERE drv_id = ?;`, - [rltm.drv_id], - ) + `DELETE FROM t_gps_tracks_rltm WHERE drv_id = ?;`, + [rltm.drv_id] + ); + } } await MysqlHelpers.queryTrx( conn, `INSERT INTO t_gps_tracks_rltm SET ? ON DUPLICATE KEY UPDATE ?;`, - [rltm, rltm], - ) + [rltm, rltm] + ); } - await MysqlHelpers.commit(conn) + // Eksekusi Commit SEBELUM melepaskan Mutex + await MysqlHelpers.commit(conn); resolve({ type: "success", result: resLogs, - }) + }); + } catch (err) { - console.log("ERROR bundleCreate2 : " + rltm.device_id) - reject(err) - } finally { - // Lock selalu dilepas, baik sukses maupun gagal, - // supaya tidak deadlock request device yang sama berikutnya. - if (lock) { + console.log("ERROR bundleCreate2 : " + (rltm.device_id || "unknown")); + + // WAJIB ROLLBACK JIKA ERROR! (Jika tidak, koneksi menggantung & menyebabkan Lock Wait Timeout) + if (conn) { try { - await rltmMutex.unlock(lock) - } catch (unlockErr) { - console.log("ERROR unlock mutex : " + rltm.device_id, unlockErr) + await MysqlHelpers.rollback(conn); + } catch (rollbackErr) { + console.log("ERROR Rollback : ", rollbackErr); } } + reject(err); + + } finally { + // MUTEX HANYA BOLEH DILEPAS SETELAH COMMIT / ROLLBACK SELESAI + if (trackLock) { + try { + await rltmMutex.unlock(trackLock); + } catch (unlockErr) { + console.log("ERROR unlock trackLock : " + logs.device_id, unlockErr); + } + } + if (lock) { + try { + await rltmMutex.unlock(lock); + } catch (unlockErr) { + console.log("ERROR unlock lock : " + rltm.device_id, unlockErr); + } + } + + // Catatan: Jika MysqlHelpers tidak otomatis melepas (release) koneksi + // ke pool setelah commit/rollback, pastikan Anda menambahkannya di sini. + // if (conn && typeof conn.release === 'function') conn.release(); } - }) + }); } static async get2() {