diff --git a/src/debug/server.ts b/src/debug/server.ts index 0f988939f..1a5413e70 100644 --- a/src/debug/server.ts +++ b/src/debug/server.ts @@ -1,3 +1,4 @@ +import { Socket } from "net"; import path from "path"; import { commands, l10n, window } from "vscode"; import IBMi from "../api/IBMi"; @@ -5,9 +6,9 @@ import { Tools } from "../api/Tools"; import { DebugConfiguration, getDebugServiceDetails, getJavaHome } from "../api/configuration/DebugConfiguration"; import { instance } from "../instantiate"; import { CustomUI } from "../webviews/CustomUI"; -export type DebugJob = { - name: string - ports: number[] +export type DebugJobs = { + server?: string + service?: string } export const MIN_DEBUG_VERSION = 3; @@ -52,9 +53,9 @@ export async function startService(connection: IBMi) { let debugConfig: DebugConfiguration; let debugConfigLoaded = false; + const config = connection.getConfig(); try { debugConfig = await new DebugConfiguration(connection).load(); - const config = connection.getConfig(); config.debugPort = debugConfig.getRemoteServiceSecuredPort(); config.debugSepPort = debugConfig.getRemoteServiceSepDaemonPort(); IBMi.connectionManager.update(config); @@ -84,31 +85,49 @@ export async function startService(connection: IBMi) { const [job] = /([^\/\s]+)\/([^\/]+)\/([^\/\s]+)/.exec(submitMessage) || []; if (job) { let tries = 0; + let debugServiceJob: string | undefined; + const debugPort = Number(debugConfig.getRemoteServiceSecuredPort()); const checkJob = async (done: (started: boolean) => void) => { - if (tries++ < 30) { - const jobDetail = await readActiveJob(connection, { name: job, ports: [] }); - if (jobDetail && typeof jobDetail === "object" && !["HLD", "MSGW", "END"].includes(String(jobDetail.JOB_STATUS))) { - if (await getDebugServiceJob()) { - window.showInformationMessage(l10n.t(`Debug service started.`)); - refreshDebugSensitiveItems(); - done(true); + if (tries++ < 40) { + if (debugServiceJob) { + if ((await getDebugEngineJobs()).service) { + //Debug service job running Java is still alive + if (await checkPort(connection, debugPort)) { + window.showInformationMessage(l10n.t(`Debug service started.`)); + refreshDebugSensitiveItems(); + done(true); + } + else { + //Job is alive but ports are not opened yet + setTimeout(() => checkJob(done), 1000); + } } else { - setTimeout(() => checkJob(done), 1000); - } - } else { - let reason; - if (typeof jobDetail === "object") { - reason = `job is in ${String(jobDetail.JOB_STATUS)} status`; - } - else if (jobDetail) { - reason = jobDetail; + //Debug service job died + window.showErrorMessage(`Debug Service job ${debugServiceJob} failed.`, 'Open logs') + .then(() => commands.executeCommand('code-for-ibmi.browse', { path: `${debugConfig.getRemoteServiceWorkDir()}/DebugService_log.txt` })); + done(false); } - else { - reason = "job has ended"; + } + else { + const jobDetail = await readActiveJob(connection, job); + if (jobDetail && typeof jobDetail === "object" && !["HLD", "MSGW", "END"].includes(String(jobDetail.JOB_STATUS))) { + debugServiceJob = (await getDebugEngineJobs()).service; + setTimeout(() => checkJob(done), 1000); + } else { + let reason; + if (typeof jobDetail === "object") { + reason = `job is in ${String(jobDetail.JOB_STATUS)} status`; + } + else if (jobDetail) { + reason = jobDetail; + } + else { + reason = "job has ended"; + } + window.showErrorMessage(`Debug Service starter job ${job} failed: ${reason}.`, 'Open output').then(() => openQPRINT(connection, job)); + done(false); } - window.showErrorMessage(`Debug Service job ${job} failed: ${reason}.`, 'Open output').then(() => openQPRINT(connection, job)); - done(false); } } else { @@ -145,30 +164,22 @@ export async function stopService(connection: IBMi) { } } -export async function getDebugServiceJob() { - const connection = instance.getConnection(); - if (connection) { - const rows = await connection.runSQL(`select job_name, local_port from qsys2.netstat_job_info j where job_name = (select job_name from qsys2.netstat_job_info j where local_port = ${connection.getConfig().debugPort || 8005} and remote_address = '0.0.0.0' fetch first row only) and remote_address = '0.0.0.0'`); - if (rows && rows.length) { - return { - name: String(rows[0].JOB_NAME), - ports: rows.map(row => Number(row.LOCAL_PORT)).sort() - } as DebugJob; - } +export async function getDebugEngineJobs(): Promise { + const rows = await instance.getConnection()?.runSQL([ + "select 'SERVER' as TYPE, JOB_NAME from table(QSYS2.ACTIVE_JOB_INFO(JOB_NAME_FILTER => 'QB5ROUTER'))", + "Union", + "select 'SERVICE' as TYPE, JOB_NAME from table(QSYS2.ACTIVE_JOB_INFO(JOB_NAME_FILTER => 'QP0ZSP*')) where JOB_USER = 'QDBGSRV'" + ].join(" ")); + + return { + server: rows?.find(row => row.TYPE === 'SERVER')?.JOB_NAME as string, + service: rows?.find(row => row.TYPE === 'SERVICE')?.JOB_NAME as string } } -export async function getDebugServerJob() { - const connection = instance.getConnection(); - if (connection) { - const [row] = await connection.runSQL(`select job_name, local_port from qsys2.netstat_job_info where cast(local_port_name as VarChar(14) CCSID 37) = 'is-debug-ile' fetch first row only`); - if (row) { - return { - name: String(row.JOB_NAME), - ports: [Number(row.LOCAL_PORT)] - } as DebugJob; - } - } +export async function isDebugEngineRunning() { + const debugJobs = await getDebugEngineJobs(); + return Boolean(debugJobs.server) && Boolean(debugJobs.service); } /** @@ -193,10 +204,6 @@ export function endJobs(jobIds: string[], connection: IBMi) { return Promise.all(promises); } -export async function isDebugEngineRunning() { - return (await Promise.all([getDebugServerJob(), getDebugServiceJob()])).every(Boolean); -} - export async function startServer() { const result = await instance.getConnection()?.runCommand({ command: "STRDBGSVR", noLibList: true }); if (result) { @@ -232,22 +239,22 @@ export function refreshDebugSensitiveItems() { commands.executeCommand("code-for-ibmi.debug.refresh"); } -export async function readActiveJob(connection: IBMi, job: DebugJob) { +export async function readActiveJob(connection: IBMi, job: string) { try { return (await connection.runSQL( - `select job_name_short "Job name", job_user "Job user", job_number "Job number", subsystem_library_name concat '/' concat subsystem as "Subsystem", authorization_name "Current user", job_status "Job status", memory_pool "Memory pool" from table(qsys2.active_job_info(job_name_filter => '${job.name.substring(job.name.lastIndexOf('/') + 1)}')) where job_name = '${job.name}' fetch first row only` + `select job_name_short "Job name", job_user "Job user", job_number "Job number", subsystem_library_name concat '/' concat subsystem as "Subsystem", authorization_name "Current user", job_status "Job status", memory_pool "Memory pool" from table(qsys2.active_job_info(job_name_filter => '${job.substring(job.lastIndexOf('/') + 1)}')) where job_name = '${job}' fetch first row only` )).at(0); } catch (error) { return String(error); } } -export async function readJVMInfo(connection: IBMi, job: DebugJob) { +export async function readJVMInfo(connection: IBMi, job: string) { try { return (await connection.runSQL(` select START_TIME "Start time", JAVA_HOME "Java Home", USER_DIRECTORY "User directory", CURRENT_HEAP_SIZE "Current memory", MAX_HEAP_SIZE "Maximum allowed memory" from QSYS2.JVM_INFO - where job_name = '${job.name}' + where job_name = '${job}' fetch first row only`)).at(0); } catch (error) { return String(error); @@ -267,4 +274,15 @@ async function openQPRINT(connection: IBMi, job: string) { else { window.showWarningMessage(`No QPRINT spooled file found for job ${job}!`); } +} + +async function checkPort(connection: IBMi, port: number) { + return await new Promise((resolve) => { + const socket = new Socket(); + socket.connect(port, connection.currentHost, () => { + socket.destroy(); + resolve(true); + }) + socket.on('error', () => resolve(false)); + }); } \ No newline at end of file diff --git a/src/instantiate.ts b/src/instantiate.ts index d4fa8a528..d2e74d2b4 100644 --- a/src/instantiate.ts +++ b/src/instantiate.ts @@ -137,8 +137,6 @@ async function onConnected() { connectedBarItem, disconnectBarItem, ].forEach(barItem => barItem.show()); - - updateConnectedBar(); } async function onDisconnected() { diff --git a/src/ui/views/debugView.ts b/src/ui/views/debugView.ts index d2e79aace..d48fe455e 100644 --- a/src/ui/views/debugView.ts +++ b/src/ui/views/debugView.ts @@ -2,7 +2,7 @@ import vscode from "vscode"; import { DebugConfiguration, getDebugServiceDetails, SERVICE_CERTIFICATE } from "../../api/configuration/DebugConfiguration"; import { Tools } from "../../api/Tools"; import { checkClientCertificate, remoteCertificatesExists } from "../../debug/certificates"; -import { DebugJob, getDebugServerJob, getDebugServiceJob, isDebugEngineRunning, readActiveJob, readJVMInfo, startServer, startService, stopServer, stopService } from "../../debug/server"; +import { getDebugEngineJobs, isDebugEngineRunning, readActiveJob, readJVMInfo, startServer, startService, stopServer, stopService } from "../../debug/server"; import { instance } from "../../instantiate"; import { VscodeTools } from "../Tools"; import { BrowserItem } from "../types"; @@ -90,27 +90,25 @@ class DebugBrowser implements vscode.TreeDataProvider { } } - return Promise.all([ - getDebugServerJob().then(debugJob => - new DebugJobItem("server", - vscode.l10n.t(`Debug Server`), - { - startFunction: startServer, - stopFunction: stopServer, - debugJob - }) - ), - getDebugServiceJob().then(debugJob => - new DebugJobItem("service", - vscode.l10n.t(`Debug Service`), { + const debugJobs = await getDebugEngineJobs(); + + return [ + new DebugJobItem("server", + vscode.l10n.t(`Debug Server`), + { + startFunction: startServer, + stopFunction: stopServer, + debugJob: debugJobs.server + }), + new DebugJobItem("service", + vscode.l10n.t(`Debug Service`), { startFunction: () => startService(connection), stopFunction: () => stopService(connection), - debugJob, - debugConfig, - certificates - }) - ) - ]); + debugJob: debugJobs.service, + debugConfig, + certificates + }) + ]; } else { return []; @@ -120,7 +118,7 @@ class DebugBrowser implements vscode.TreeDataProvider { async resolveTreeItem(item: vscode.TreeItem, element: BrowserItem, token: vscode.CancellationToken) { const connection = instance.getConnection(); if (connection && element.tooltip === undefined && element instanceof DebugJobItem && element.parameters.debugJob) { - element.tooltip = new vscode.MarkdownString(`${vscode.l10n.t("Listening on port(s)")} ${element.parameters.debugJob.ports.join(", ")}\n\n`); + element.tooltip = new vscode.MarkdownString(); const activeJob = await readActiveJob(connection, element.parameters.debugJob); if (activeJob) { const jobToMarkDown = (job: Tools.DB2Row | string) => typeof job === "string" ? job : Object.entries(job).filter(([key, value]) => value !== null).map(([key, value]) => `- ${vscode.l10n.t(key)}: ${value}`).join("\n"); @@ -151,7 +149,7 @@ class DebugJobItem extends DebugItem { constructor(readonly type: "server" | "service", label: string, readonly parameters: { startFunction: () => Promise, stopFunction: () => Promise, - debugJob?: DebugJob, + debugJob?: string, certificates?: Certificates, debugConfig?: DebugConfiguration }) { @@ -184,7 +182,7 @@ class DebugJobItem extends DebugItem { this.problem = problem; if (running) { - this.description = this.parameters.debugJob!.name; + this.description = this.parameters.debugJob; } else { this.description = vscode.l10n.t(`Offline`);