Skip to content

Commit 35e01b0

Browse files
diningPhilosopher64Prabhakar Kumar
authored andcommitted
matlab-proxy now warns instead of erroring out when Xvfb is not found on Linux Systems.
* Makes Xvfb optional on Linux. * Added infrastructure to display warnings on the status information modal. fixes mathworks/jupyter-matlab-proxy#75
1 parent d92b461 commit 35e01b0

File tree

11 files changed

+199
-59
lines changed

11 files changed

+199
-59
lines changed

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,10 @@ The MATLAB Proxy is under active development. For support or to report issues, s
4747

4848
$ sudo yum install xorg-x11-server-Xvfb
4949
```
50+
51+
*Note: The installation of Xvfb is **optional** (w.e.f. v0.11.0 of matlab-proxy). However, we highly recommend installing it.*
5052
* Python versions: **3.8** | **3.9** | **3.10** | **3.11**
5153
* [Browser Requirements](https://www.mathworks.com/support/requirements/browser-requirements.html)
52-
5354
* Supported Operating Systems:
5455
* Linux®
5556
* Windows® Operating System ( starting v0.4.0 of matlab-proxy )

gui/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@
4040
"last 1 safari version"
4141
]
4242
},
43-
"proxy": "http://127.0.0.1:8000",
43+
"proxy": "http://127.0.0.1:8888",
4444
"homepage": "./",
4545
"devDependencies": {
4646
"@babel/preset-env": "^7.11.0",

gui/src/components/Information/Information.css

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/* Copyright (c) 2020-2022 The MathWorks, Inc. */
22

3-
#information .alert:not(.error-container) {
3+
#information .alert:not(.error-container, .warnings-container) {
44
padding: 0;
55
margin-bottom: 0;
66
background: white;
@@ -24,37 +24,39 @@
2424
margin-bottom: 0;
2525
}
2626

27-
#information .error-msg {
27+
#information .error-msg, .warnings-msg {
2828
white-space: pre;
2929
font: 10pt monospace;
3030
}
3131

32-
#information .error-container.alert {
32+
#information .error-container.alert, .warnings-container.alert {
3333
border-radius: 0;
3434
margin: 5px 20px;
3535
padding: 10px;
3636
}
3737

38-
#information .error-container.alert p {
38+
#information .error-container.alert p, .warnings-container.alert p {
3939
margin: 0;
4040
}
4141

42-
#information .error-container.alert .error-text {
42+
#information .error-container.alert .error-text,
43+
.warnings-container.alert .warnings-text {
4344
margin-left: 30px;
4445
}
4546

46-
#information .error-logs-container .error-container.alert {
47+
#information .error-logs-container .error-container.alert,
48+
.warnings-container .warnings-container.alert {
4749
padding: 0;
4850
/* if this is inside an expand-collapse, padding just gets in the way */
4951
}
5052

51-
#information .error-msg {
53+
#information .error-msg, .warnings-msg {
5254
padding: 10px;
5355
max-height: 200px;
5456
overflow: auto;
5557
}
5658

57-
#information .error-logs-container .expand_trigger {
59+
#information .error-logs-container .warnings-container .expand_trigger {
5860
padding: 10px 5px 5px 40px;
5961
font-size: 15px;
6062
}
@@ -156,4 +158,4 @@
156158
padding-top: 5px;
157159
padding-bottom: 0.3em;
158160
max-width: 10ch;
159-
}
161+
}

gui/src/components/Information/index.js

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import Linkify from 'react-linkify';
77
import {
88
selectLicensingInfo,
99
selectError,
10+
selectWarnings,
1011
selectOverlayHidable,
1112
selectInformationDetails,
1213
selectAuthEnabled,
@@ -22,6 +23,7 @@ function Information({
2223
}) {
2324
const licensingInfo = useSelector(selectLicensingInfo);
2425
const error = useSelector(selectError);
26+
const warnings = useSelector(selectWarnings);
2527
const overlayHidable = useSelector(selectOverlayHidable);
2628

2729
const [token, setToken] = useState('');
@@ -33,9 +35,7 @@ function Information({
3335
const tokenInput = useRef();
3436

3537
const [errorLogsExpanded, setErrorLogsExpanded] = useState(false);
36-
const errorLogsExpandedToggle = () => {
37-
setErrorLogsExpanded(!errorLogsExpanded);
38-
};
38+
const [warningsExpanded, setWarningsExpanded] = useState(true);
3939

4040
let info;
4141
switch (licensingInfo?.type) {
@@ -73,8 +73,8 @@ function Information({
7373

7474
const errorLogsNode = (error && error.logs !== null && error.logs.length > 0) ? (
7575
<div className="expand_collapse error-logs-container">
76-
<h4 className={`expand_trigger ${errorLogsExpanded ? 'expanded' : 'collapsed'}`}
77-
onClick={errorLogsExpandedToggle}>
76+
<h4 className={`expand_trigger ${errorLogsExpanded ? 'expanded' : 'collapsed'}`}
77+
onClick={() => setErrorLogsExpanded(!errorLogsExpanded)}>
7878
<span className="icon-arrow-open-down"></span>
7979
<span className="icon-arrow-open-right"></span>
8080
Error logs
@@ -89,6 +89,31 @@ function Information({
8989
</div>
9090
) : null;
9191

92+
const linkDecorator = (href, text, key) => (
93+
<a href={href} key={key} target="_blank" rel="noopener noreferrer">
94+
{text}
95+
</a>
96+
);
97+
98+
99+
const warningsNode = (warnings && warnings.length > 0) ? (
100+
<div className="expand_collapse warnings-container">
101+
<h4 className={`expand_trigger ${warningsExpanded ? 'expanded' : 'collapsed'}`}
102+
onClick={() => setWarningsExpanded(!warningsExpanded)}>
103+
<span className="icon-arrow-open-down"></span>
104+
<span className="icon-arrow-open-right"></span>
105+
Warnings
106+
</h4>
107+
<div id="warnings"
108+
className={`expand_target warnings-container alert alert-warning ${warningsExpanded ? 'expanded' : 'collapsed'}`}
109+
aria-expanded={warningsExpanded}>
110+
<Linkify componentDecorator={linkDecorator}>
111+
<div className="warnings-msg">{warnings.map((warning, index) => (index + 1).toString() + ")" + warning.trim()).join("\n\n")}</div>
112+
</Linkify>
113+
</div>
114+
</div>
115+
) : null;
116+
92117
const onCloseClick = event => {
93118
if (event.target === event.currentTarget) {
94119
event.preventDefault();
@@ -197,6 +222,7 @@ function Information({
197222
</div>
198223
{errorMessageNode}
199224
{errorLogsNode}
225+
{warningsNode}
200226
</div>
201227
<div className="modal-footer">
202228
{children}

gui/src/reducers/index.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,16 @@ export function loadUrl(state = null, action) {
253253
}
254254
}
255255

256+
export function warnings(state = null, action) {
257+
switch (action.type) {
258+
case RECEIVE_SERVER_STATUS:
259+
const warnings = action.status.warnings;
260+
return warnings.length > 0 ? warnings : null;
261+
default:
262+
return state;
263+
}
264+
}
265+
256266
export function error(state = null, action) {
257267
switch (action.type) {
258268
case SET_AUTH_STATUS:
@@ -328,6 +338,7 @@ export default combineReducers({
328338
serverStatus,
329339
loadUrl,
330340
error,
341+
warnings,
331342
envConfig,
332343
useMOS,
333344
useMRE,

gui/src/selectors/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ export const selectLicensingInfo = state => state.serverStatus.licensingInfo;
1515
export const selectServerStatusFetchFailCount = state => state.serverStatus.fetchFailCount;
1616
export const selectLoadUrl = state => state.loadUrl;
1717
export const selectError = state => state.error;
18+
export const selectWarnings = state => state.warnings;
1819
export const selectUseMOS = state => state.useMOS === true;
1920
export const selectUseMRE = state => state.useMRE === true;
2021
export const selectAuthEnabled = state => state.authentication.enabled;

matlab_proxy/app.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ async def create_status_response(app, loadUrl=None):
113113
"licensing": marshal_licensing_info(state.licensing),
114114
"loadUrl": loadUrl,
115115
"error": marshal_error(state.error),
116+
"warnings": state.warnings,
116117
"wsEnv": state.settings.get("ws_env", ""),
117118
}
118119
)

matlab_proxy/app_state.py

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ def __init__(self, settings):
8484

8585
# Initialize with the error state from the initialization of settings
8686
self.error = settings["error"]
87+
self.warnings = settings["warnings"]
8788

8889
if self.error is not None:
8990
self.logs["matlab"].clear()
@@ -262,7 +263,10 @@ def _are_required_processes_ready(
262263
xvfb_process = self.processes["xvfb"]
263264

264265
if system.is_linux():
265-
if xvfb_process is None or xvfb_process.returncode is not None:
266+
# If Xvfb is on system PATH, check if it up and running.
267+
if self.settings["is_xvfb_available"] and (
268+
xvfb_process is None or xvfb_process.returncode is not None
269+
):
266270
logger.debug(
267271
"Xvfb has not started"
268272
if xvfb_process is None
@@ -671,9 +675,22 @@ async def __setup_env_for_matlab(self) -> dict:
671675
# DDUX info for MATLAB
672676
matlab_env["MW_CONTEXT_TAGS"] = self.settings.get("mw_context_tags")
673677

678+
# Update DISPLAY env variable for MATLAB only if it was supplied by Xvfb.
674679
if system.is_linux():
675-
# Adding DISPLAY key which is only available after starting Xvfb successfully.
676-
matlab_env["DISPLAY"] = self.settings["matlab_display"]
680+
if self.settings.get("matlab_display", None):
681+
matlab_env["DISPLAY"] = self.settings["matlab_display"]
682+
logger.info(
683+
f"Using the display number supplied by Xvfb process:{matlab_env['DISPLAY']} for launching MATLAB"
684+
)
685+
else:
686+
if "DISPLAY" in matlab_env:
687+
logger.info(
688+
f"Using the existing DISPLAY environment variable with value:{matlab_env['DISPLAY']} for launching MATLAB"
689+
)
690+
else:
691+
logger.info(
692+
"No DISPLAY environment variable found. Launching MATLAB without it."
693+
)
677694

678695
# The matlab ready file is written into this location(self.mwi_logs_dir) by MATLAB
679696
# The mwi_logs_dir is where MATLAB will write any subsequent logs
@@ -968,8 +985,8 @@ async def start_matlab(self, restart_matlab=False):
968985
self.error = None
969986
self.logs["matlab"].clear()
970987

971-
# Start Xvfb process if in a posix system
972-
if system.is_linux():
988+
# Start Xvfb process on linux if possible
989+
if system.is_linux() and self.settings["is_xvfb_available"]:
973990
xvfb = await self.__start_xvfb_process()
974991

975992
# xvfb variable would be None if creation of the process failed.

matlab_proxy/settings.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,8 @@ def get_dev_settings(config):
199199
"mwi_use_existing_license": mwi.validators.validate_use_existing_licensing(
200200
os.getenv(mwi_env.get_env_name_mwi_use_existing_license(), "")
201201
),
202+
"warnings": [],
203+
"is_xvfb_available": False,
202204
}
203205

204206

@@ -243,13 +245,21 @@ def get(config_name=matlab_proxy.get_default_config_name(), dev=False):
243245
settings["nlm_conn_str"] = "123@nlm"
244246

245247
else:
246-
settings = {"error": None}
248+
settings = {"error": None, "warnings": []}
247249

248250
# Initializing server settings separately allows us to return
249251
# a minimal set of settings required to launch the server even if
250252
# there is an exception thrown when creating the matlab specific settings.
251253
settings.update(get_server_settings(config_name))
252254

255+
settings["is_xvfb_available"] = True if shutil.which("Xvfb") else False
256+
257+
# Warn user if xvfb is not available on system path.
258+
if system.is_linux() and not settings["is_xvfb_available"]:
259+
warning = " Unable to find Xvfb on the system PATH. Xvfb enables graphical abilities like plots and figures in the MATLAB desktop.\nConsider adding Xvfb to the system PATH and restart matlab-proxy. See https://github.com/mathworks/matlab-proxy#requirements for information."
260+
logger.warning(warning)
261+
settings["warnings"].append(warning)
262+
253263
try:
254264
# Update settings with matlab specific values.
255265
settings.update(get_matlab_settings())

0 commit comments

Comments
 (0)