Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closing RPE app in Windows leaves the backend server running in some instances causing clashes on new rpe instance #263

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion .github/workflows/rpe_test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,13 @@ jobs:
if: matrix.os == 'ubuntu-latest' && always()
run: find $HOME -type f -name "rpe.log" -exec cat {} +


- name: E2E Playwright shut down test on Linux latest
if: ${{ matrix.os == 'ubuntu-latest' }}
run: |
rm -rf $HOME/rpe.log && xvfb-run --auto-servernum --server-args="-screen 0 1280x960x24" -- npx playwright test -- restart.test.js
cat $HOME/rpe.log | tail -n 2 | grep "Shutting down server..."
[[ $? != 0 ]] && exit 1

- name: Run ESLint only on ubuntu-latest
if: ${{ matrix.os == 'ubuntu-latest' }}
run: npx eslint src/
Expand Down
32 changes: 32 additions & 0 deletions backend/api/shutdown.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import signal
import sys
from flask import Blueprint, request, jsonify
from submodule.rs_logger import log

shutdown_api = Blueprint('shutdown_api', __name__)

def shutdown_server():
"""Function to gracefully shut down the server."""
log("Shutting down server...")
func = request.environ.get('werkzeug.server.shutdown')
if func is not None:
func()

Check warning on line 13 in backend/api/shutdown.py

View check run for this annotation

Codecov / codecov/patch

backend/api/shutdown.py#L10-L13

Added lines #L10 - L13 were not covered by tests
else:
log("Server shutdown function not found.", level="ERROR")
sys.exit(0)

Check warning on line 16 in backend/api/shutdown.py

View check run for this annotation

Codecov / codecov/patch

backend/api/shutdown.py#L15-L16

Added lines #L15 - L16 were not covered by tests

def signal_handler(signal_received, frame):
"""Handles signals for graceful shutdown."""
log(f"Signal {signal_received} received, initiating shutdown...")
shutdown_server()

Check warning on line 21 in backend/api/shutdown.py

View check run for this annotation

Codecov / codecov/patch

backend/api/shutdown.py#L20-L21

Added lines #L20 - L21 were not covered by tests

@shutdown_api.route('/shutdown', methods=['POST'])
def trigger_shutdown():
"""API endpoint to trigger a server shutdown."""
log("Shutdown API called by user request.")
shutdown_server()
return jsonify({"message": "Server is shutting down..."}), 200

Check warning on line 28 in backend/api/shutdown.py

View check run for this annotation

Codecov / codecov/patch

backend/api/shutdown.py#L26-L28

Added lines #L26 - L28 were not covered by tests

# Register the signal handler for SIGINT and SIGTERM
signal.signal(signal.SIGINT, signal_handler)
signal.signal(signal.SIGTERM, signal_handler)
99 changes: 49 additions & 50 deletions backend/restapi_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import argparse
import os
import sys
from flask import Flask, request, jsonify
from flask import Flask, request
from flasgger import Swagger
from submodule.rs_device_manager import RsDeviceManager
from submodule.rs_logger import log_setup, log, RsLogLevel
Expand All @@ -18,73 +18,72 @@
from api.peripherals import peripherals_api
from api.utils import attrs_api
from api.project import project_api
from api.shutdown import shutdown_api #shutdown api

# Create Flask app
app = Flask(__name__)
app.url_map.strict_slashes = False

# Set up Swagger for API documentation
swagger_template = {
"swagger": "2.0",
"info": {
"title": "RPE Backend API",
"description": "The RPE Backend APIs which consumed by the RPE frontend for power and thermal estimation of the Rapid Silicon devices.",
"version": "0.1.0"
}
}
swagger = Swagger(app, template=swagger_template)

# Register API blueprints with the Flask app
app.register_blueprint(device_api)
app.register_blueprint(clock_api)
app.register_blueprint(dsp_api)
app.register_blueprint(fabric_le_api)
app.register_blueprint(bram_api)
app.register_blueprint(io_api)
app.register_blueprint(peripherals_api)
app.register_blueprint(attrs_api)
app.register_blueprint(project_api)
app.register_blueprint(shutdown_api) # Register shutdown API

# Hook up request signal to log requests by UI
@app.before_request
def before_request():
log(f"{request.method} {request.url}")

Check warning on line 53 in backend/restapi_server.py

View check run for this annotation

Codecov / codecov/patch

backend/restapi_server.py#L53

Added line #L53 was not covered by tests

@app.after_request
def after_request(response):
log(f"{request.method} {request.url} {response.status_code} - DONE")
return response

Check warning on line 58 in backend/restapi_server.py

View check run for this annotation

Codecov / codecov/patch

backend/restapi_server.py#L57-L58

Added lines #L57 - L58 were not covered by tests

#
# Main entry point
#
def main():
# create and parse command line args
parser = argparse.ArgumentParser(description='Rapid Power Estimator Rest API Server command-line arguments.')
parser.add_argument('device_file', type=str, help='Path to the input device xml file')
# Parse command-line arguments
parser = argparse.ArgumentParser(description='Rapid Power Estimator REST API Server command-line arguments.')
parser.add_argument('device_file', type=str, help='Path to the input device XML file')
parser.add_argument('--port', type=int, default=5000, help='Specify TCP Port to use for REST server')
parser.add_argument('--debug', default=False, action='store_true', help='Enable/Disable debug mode')
parser.add_argument('--logfile', type=str, default="rpe.log", help='Specify log file name')
parser.add_argument('--maxbytes', type=int, default=2048, help='Specify maximun log file size in kilobytes before rollover')
parser.add_argument('--backupcount', type=int, default=20, help='Specify no. of backup log files')
parser.add_argument('--maxbytes', type=int, default=2048, help='Specify maximum log file size in kilobytes before rollover')
parser.add_argument('--backupcount', type=int, default=20, help='Specify number of backup log files')
args = parser.parse_args()

# setup app logger
log_setup(filename=args.logfile, max_bytes=args.maxbytes*1024, backup_count=args.backupcount)
# Set up logger
log_setup(filename=args.logfile, max_bytes=args.maxbytes * 1024, backup_count=args.backupcount)

# Check if the device_file exists
if os.path.exists(args.device_file) == False:
if not os.path.exists(args.device_file):
log(f"Device file '{args.device_file}' does not exist.", RsLogLevel.ERROR)
sys.exit(1)

# Parse Device XML file into Device List
devicemanager = RsDeviceManager.get_instance()
devicemanager.load_xml(args.device_file)

# create flask app object
app = Flask(__name__)
app.url_map.strict_slashes = False

# auto generate restapi documentation
template = {
"swagger": "2.0",
"info": {
"title": "RPE Backend API",
"description": "The RPE Backend APIs which consumed by the RPE frontend for power and thermal estimation of the Rapid Silicon devices.",
"version": "0.1.0"
}
}
swagger = Swagger(app, template=template)

# bind device api objects onto the flask app
app.register_blueprint(device_api)
app.register_blueprint(clock_api)
app.register_blueprint(dsp_api)
app.register_blueprint(fabric_le_api)
app.register_blueprint(bram_api)
app.register_blueprint(io_api)
app.register_blueprint(peripherals_api)
app.register_blueprint(attrs_api)
app.register_blueprint(project_api)

# hook up request signal to log request by UI
@app.before_request
def before_request():
log(f"{request.method} {request.url}")

@app.after_request
def after_request(response):
log(f"{request.method} {request.url} {response.status_code} - DONE")
return response

# log app server started
# Log server start message
log("App server is running...")

# Start Rest API server
# Start the Flask app
app.run(debug=args.debug, port=args.port)

if __name__ == "__main__":
Expand Down
12 changes: 11 additions & 1 deletion cleanup.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
const { shutdown } = require('./src/utils/serverAPI');
const isWindows = process.platform === 'win32';

const kill = (process) => {
const kill = async (process) => {
try {
// calling the shutdown API
await shutdown();
console.log('Shutdown API called successfully.');
} catch (error) {
console.error('Error calling shutdown API:', error);
}

// Fallback if API fails or for local process termination
if (isWindows) {
const kill = require('tree-kill');
kill(process.pid);
Expand Down
9 changes: 8 additions & 1 deletion main.js
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,14 @@ const template = [
},
},
{ type: 'separator' },
{ role: 'quit' },
//{ role: 'quit' },
{
label: 'Exit',
click: () => {
kill(serverProcess); // just making sure shutdown api is called
app.quit(); // close the application
},
},
],
},
// {
Expand Down
89 changes: 61 additions & 28 deletions src/utils/serverAPI.js
Original file line number Diff line number Diff line change
@@ -1,32 +1,47 @@
const config = require('../../rpe.config.json');

const { server } = config;
let { port } = config;

export function devices() { return `${server}:${port}/devices`; }
export function attributes() { return `${server}:${port}/attributes`; }
export function project() { return `${server}:${port}/project`; }
export function projectClose() { return `${project()}/close`; }
export function projectOpen() { return `${project()}/open`; }
export function projectSave() { return `${project()}/create`; }
function devices() {
return `${server}:${port}/devices`;

Check warning on line 6 in src/utils/serverAPI.js

View check run for this annotation

Codecov / codecov/patch

src/utils/serverAPI.js#L5-L6

Added lines #L5 - L6 were not covered by tests
}

function attributes() {
return `${server}:${port}/attributes`;

Check warning on line 10 in src/utils/serverAPI.js

View check run for this annotation

Codecov / codecov/patch

src/utils/serverAPI.js#L9-L10

Added lines #L9 - L10 were not covered by tests
}

function project() {
return `${server}:${port}/project`;

Check warning on line 14 in src/utils/serverAPI.js

View check run for this annotation

Codecov / codecov/patch

src/utils/serverAPI.js#L13-L14

Added lines #L13 - L14 were not covered by tests
}

function projectClose() {
return `${project()}/close`;

Check warning on line 18 in src/utils/serverAPI.js

View check run for this annotation

Codecov / codecov/patch

src/utils/serverAPI.js#L17-L18

Added lines #L17 - L18 were not covered by tests
}

function projectOpen() {
return `${project()}/open`;

Check warning on line 22 in src/utils/serverAPI.js

View check run for this annotation

Codecov / codecov/patch

src/utils/serverAPI.js#L21-L22

Added lines #L21 - L22 were not covered by tests
}

function projectSave() {
return `${project()}/create`;

Check warning on line 26 in src/utils/serverAPI.js

View check run for this annotation

Codecov / codecov/patch

src/utils/serverAPI.js#L25-L26

Added lines #L25 - L26 were not covered by tests
}

export function setPort(p, fetchDevices) {
function setPort(p, fetchDevices) {

Check warning on line 29 in src/utils/serverAPI.js

View check run for this annotation

Codecov / codecov/patch

src/utils/serverAPI.js#L29

Added line #L29 was not covered by tests
if (p !== undefined) {
port = p;
// eslint-disable-next-line no-use-before-define
GET(devices(), fetchDevices);
}
}

export function peripheralPath(deviceId, url) {
function peripheralPath(deviceId, url) {

Check warning on line 36 in src/utils/serverAPI.js

View check run for this annotation

Codecov / codecov/patch

src/utils/serverAPI.js#L36

Added line #L36 was not covered by tests
return `${devices()}/${deviceId}/peripherals/${url}`;
}

export function deviceInfo(deviceId) {
function deviceInfo(deviceId) {

Check warning on line 40 in src/utils/serverAPI.js

View check run for this annotation

Codecov / codecov/patch

src/utils/serverAPI.js#L40

Added line #L40 was not covered by tests
return `${devices()}/${deviceId}`;
}

export const Elem = {
const Elem = {
clocking: 'clocking',
io: 'io',
bram: 'bram',
Expand All @@ -35,7 +50,7 @@
peripherals: 'peripherals',
};

export const api = {
const api = {
fetch(func, deviceId) {
return `${devices()}/${deviceId}/${func}`;
},
Expand All @@ -49,46 +64,64 @@
},
};

export function POST(url, data, callback) {
function POST(url, data, callback) {

Check warning on line 67 in src/utils/serverAPI.js

View check run for this annotation

Codecov / codecov/patch

src/utils/serverAPI.js#L67

Added line #L67 was not covered by tests
fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
}).then((response) => {
if (response.ok) {
if (callback) callback();
}
if (response.ok && callback) callback();

Check warning on line 73 in src/utils/serverAPI.js

View check run for this annotation

Codecov / codecov/patch

src/utils/serverAPI.js#L73

Added line #L73 was not covered by tests
});
}

export function DELETE(url, callback) {
function DELETE(url, callback) {

Check warning on line 77 in src/utils/serverAPI.js

View check run for this annotation

Codecov / codecov/patch

src/utils/serverAPI.js#L77

Added line #L77 was not covered by tests
fetch(url, {
method: 'DELETE',
}).then((response) => {
if (response.ok) {
if (callback) callback();
}
if (response.ok && callback) callback();

Check warning on line 81 in src/utils/serverAPI.js

View check run for this annotation

Codecov / codecov/patch

src/utils/serverAPI.js#L81

Added line #L81 was not covered by tests
});
}

export function PATCH(url, data, callback) {
function PATCH(url, data, callback) {

Check warning on line 85 in src/utils/serverAPI.js

View check run for this annotation

Codecov / codecov/patch

src/utils/serverAPI.js#L85

Added line #L85 was not covered by tests
fetch(url, {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
}).then((response) => {
if (response.ok) {
if (callback) callback();
} else {
// todo handle error
}
if (response.ok && callback) callback();

Check warning on line 91 in src/utils/serverAPI.js

View check run for this annotation

Codecov / codecov/patch

src/utils/serverAPI.js#L91

Added line #L91 was not covered by tests
});
}

export function GET(url, callback) {
function GET(url, callback) {

Check warning on line 95 in src/utils/serverAPI.js

View check run for this annotation

Codecov / codecov/patch

src/utils/serverAPI.js#L95

Added line #L95 was not covered by tests
fetch(url)
.then((response) => response.json())
.then((data) => {
if (callback) callback(data);
});
}

// Function to call the shutdown API
function shutdown(callback) {
const shutdownUrl = `${server}:${port}/shutdown`;
POST(shutdownUrl, null, callback);

Check warning on line 106 in src/utils/serverAPI.js

View check run for this annotation

Codecov / codecov/patch

src/utils/serverAPI.js#L104-L106

Added lines #L104 - L106 were not covered by tests
}

// Exporting functions using CommonJS syntax
module.exports = {
devices,
attributes,
project,
projectClose,
projectOpen,
projectSave,
setPort,
peripheralPath,
deviceInfo,
Elem,
api,
POST,
DELETE,
PATCH,
GET,
shutdown,
};
Loading
Loading