25 May 2018

Learning VS Code source

History / Edit / PDF / EPUB / BIB / 9 min read (~1639 words)

  • Read package.json to discover what packages VS Code depends on
  • Observe the root directory structure, and more specifically the extensions and src directories which contain the bulk of the source code
    • A lot of the code in the extensions directory appears to be dedicated to programming language support
      • The remainder of the extensions seem to provide functionality for things that aren't "core" to vscode, such as configuration-editing, emmet, extension-editing and some color themes
  • If you look at the .vscode/launch.json, you will find all the tasks that can be executed from within VS Code debugger. One task of interest is Launch VS Code which will take care of launching VS Code for us so that we may debug it
    • In this file you will also discover that it runs ${workspaceFolder}/scripts/code.bat, which is the next script we'll take a look at
  • In ./scripts/code.bat, we discover that this script will run yarn if the node_modules directory is missing, download the electron binaries if necessary and call gulp compile if the out directory is missing, then finally start the electron/vs code binary in the .build/electron directory
  • We then start to look for common entry points file such as index.ts/js or main.ts/js, for which we find a match in the src directory
  • We take a quick look around, trying to find where electron is likely to be instantiated... There's a lot of code in src/main.js that would be better elsewhere to make it easier to navigate this file
  • Close to the bottom of the file we discover the code we are interested in as a call to app.once('ready', ...)
    • Once the app is ready, we want to call src/bootstrap-amd and pass vs/code/electron-main/main as our entry point (per the signature of the exported function in ./src/bootstrap-amd)
      • Here we can go to two places, either src/bootstrap-amd or src/vs/code/electron-main/main
        • We take a quick peek at both files and we can quickly tell that src/bootstrap-amd is used mainly to load src/vs/code/electron-main/main which is the file we're going to be interested in
  • Once again, we quickly look around src/vs/code/electron-main/main and find that the main logic is at the bottom of the file
  • First the command line arguments are parsed
  • Then services are bootstrapped/instantiated
  • Finally the CodeApplication is started up
  • This leads us to look into src/vs/code/electron-main/app.ts
  • As the file is quite large, we start by skimming through it, looking at the available methods on the CodeApplication class as well as its properties
  • Looking at the constructor, we can see that a lot of objects are given to it. We also observe the use of the @... syntax (those are decorators)
    • In this case (and for most constructors), this is how VS Code does service (dependencies) injection
  • One will also notice that most, if not all parameters have a visibility assigned to it. What this does is that it will create an associated property in the class as well as assigning the parameter value to this property in the constructor. Thus, instead of writing

    
    class AnotherClass {
    private someClass: SomeClass;
    
    constructor(someClass: SomeClass) {
        this.someClass = someClass;
    }
    }
    

    you simply write

    
    class AnotherClass {
    constructor(private someClass: SomeClass) {
    }
    }
    
  • Upon its creation, the CodeApplication class will register various event listeners on the electron app object
  • If we remember, in src/vs/code/electron-main/main, after the CodeApplication object is instantiated, we call startup() on it. So, we want to take a look at what that method does
  • Without knowing too much about the VS Code source, it appears that we are instantiating an IPC server (inter-process communication) and then the shared process
  • After that is done, we initialize some more services in CodeApplication::initServices, such as the update service (which I guess takes care of checking for VS Code updates) and the telemetry (data about VS Code feature usage)
  • We finally get to the point where we're about to open a window in CodeApplication::openFirstWindow!
    • This leads us to go read the class WindowsManager in src/vs/code/electron-main/windows.ts. Once again, this file is pretty large, so we want to skim it to see what it contains (functions, classes, properties, methods)
  • There are a few large classes in src/vs/code/electron-main/windows.ts that I'd want to extract to make the file smaller and simpler (less cognitive load). However, the issue is that those classes are not declared as exported, and thus are only available in the local file. It would be possible to move these classes to other files and import them, but by doing so it would also "communicate" that others can use it, which is what having the classes as not exported prevents, at the cost of making single files larger and harder to comprehend
  • We know that the constructor is first called, then from CodeApplication::openFirstWindow, we see that WindowsManager::ready and WindowsManager::open are both called.
    • In the constructor we instantiate the Dialogs class (takes care of open/save dialog windows) and the WorkspacesManager class (takes care of workspace management, such as open/save)
    • In ready event listeners are registered
    • In open there is a lot of logic associated with the window finally opening

  • If you start VS Code using the debug feature, you will not be able to open the Chrome DevTools (at this moment, 2018-05-26) because only 1 process is allowed to attach to the Chrome DevTools instance, and that process is the VS Code editor that started the debugged VS Code instance

Today I want to find out how VS Code restores a windows sessions when you start it. Apparently, if you run it as code ., it will not restore the same set of windows than if you called it simply with code.

  • In src/vs/code/electron-main/launch.ts, the LaunchService::startOpenWindow appears to implement logic based on how many arguments were given. In all cases, we end up doing a call to the IWindowsMainService::open method.
    • Note that in both cases, the path we're opening is within the args variable, which is passed to the cli property of the IOpenConfiguration object.
  • The implementation of IWindowsMainService we are interested in lives in src/vs/code/electron-main/windows.ts.
  • In the WindowsManager::open method, we rapidly discover that the windows that will be opened will be retrieved in WindowsManager::getPathsToOpen. In there, we can observe that the windows that will be opened depend on whether something was passed from the API, we forced an empty window, we're extracting paths from the cli or we should restore from the previous session.
    • If we arrive at this last case, we can see that the logic is to call WindowsManager::doGetWindowsFromLastSession, which is pretty self-explanatory, and will retrieve the previous set of windows from the last session. This is what happens when you start code using code
    • In the case where we pass a path, this path is in openConfig.cli._. In this case, the windows that were previously opened, and part of this.windowsState.openedWindows (where this is a WindowsManager object)
      • Here we wonder how the windowsState.openedWindows state gets restored on VS Code start. To figure that out, we start at the WindowsManager.constructor method. There we find this.windowsState = this.stateService.getItem<IWindowsState>(WindowsManager.windowsStateStorageKey) || { openedWindows: [] };, which states to use get a IWindowState object from the stateService if one exists or to create an object with no opened windows. If we assume that this windows state is the same regardless of how we start VS Code, then it is not there that the difference in opened windows will occur.
01 Mar 2015

PHP Startup Internals

History / Edit / PDF / EPUB / BIB / 8 min read (~1427 words)
php startup internals
  • PHP
  • MySQL
  • Jenkins
  • Apache/Nginx
  • Linux (Ubuntu)
  • node.js/io.js

My goal with this post (and any subsequent posts) is to share my thoughts and current practices on the topic of developing PHP applications in a startup environment.

Starting a new startup means making decisions. Which framework to choose, what tool to use, which programming language, what task should be done before this other task, etc.

Starting is often overwhelming. What should be done first? If we ignore all the questions about the business (what sector? any specific niche? what sort of product?), then the first thing that an individual or a team should aim for is to prepare for iteration.

Many would start by working directly on their first project. It makes sense since it is the primary goal of your startup to produce results. However, writing code without establishing some sort of workflow framework will be inefficient.

My first step is generally to setup Jenkins, a continuous integration tool. It allows me to setup automated testing and automated deployment to a development/staging area/environment. This is useful for two purposes:

  1. Having an external "party" execute the test in their own environment (separate from mine). This validates that whatever is in source control will work on someone else computer.
  2. It deploys automatically "stable" (in the sense that they pass testing) version to an online facing server. With automated deployment, it is possible for me to keep on writing code, have it tested and then deployed to a server where I can ask others to take a look at and provide feedback.

There are a couple of way to get setup.

Everything will be setup on the same machine. Here is how it basically goes:

  1. Install jenkins
  2. Create two jenkins jobs, project-name-develop which takes care of building the develop branch of your repository and run the tests (basic continuous integration), and project-name-develop-to-development, which will again, build the develop branch of your repository but this time for the purpose of having it available online.

There won't be much to discuss here except a list of plugins that are almost mandatory (either because they make jenkins much more useful or allow you to more quickly diagnose issues).

  • AnsiColor
  • Checkstyle Plug-in
  • Clover PHP plugin
  • Credentials Plugin
  • Duplicate Code Scanner Plug-in
  • GIT client plugin
  • GIT plugin
  • HTML Publisher plugin
  • JDepend Plugin
  • JUnit Plugin
  • Mailer Plugin
  • Matrix Authorization Strategy Plugin
  • Matrix Project Plugin
  • Node and Label parameter plugin
  • Parameterized Trigger plugin
  • Plot plugin
  • PMD Plug-in
  • Self-Organizing Swarm Plug-in Modules
  • Slack Notification Plugin
  • SSH Credentials Plugin
  • SSH Slaves plugin
  • Static Analysis Utilities
  • Throttle Concurrent Builds Plug-in
  • Timestamper
  • Violations plugin
  • xUnit plugin

I'll now go into more details as to what each does.

  1. Pull the latest revision from the repository
  2. Download and update composer (if required)
  3. Install dependencies
    1. bower install
    2. npm install
    3. composer install
  4. Build assets to validate they compile
    1. Compile LESS into CSS
    2. Concatenate and minify JS
  5. Prepare the application environment
    1. Migrate database
    2. Seed database
  6. Run continuous integration tools to assert code quality
    1. phpunit
    2. phploc
    3. pdepend
    4. phpmd
    5. phpcs
    6. phpcpd

An iterative cycle here should take less than 5 minutes (and a maximum of 30 minutes). The goal is to quickly know after pushing changes to your repository that nothing is broken.

For this to work, you simply need to make a symbolic link from the jenkins project workspace to some path which apache/nginx makes available to external users. For example

/home/jenkins/workspace/project-a-develop-to-development/public -> /var/www/development/project-a

  1. Pull the latest revision from the repository
  2. Download and update composer (if required)
  3. Install dependencies
    1. bower install
    2. npm install
    3. composer install
  4. Build/Prepare website
    1. Compile LESS into CSS
    2. Concatenate and minify JS
  5. Prepare the application environment
    1. Migrate database
    2. Seed database

An iterative cycle here should take less than 5 minutes. Anything that takes longer than that would be suspicious.

Now that you have both projects setup, here's how things work. First, project-name-develop is triggered every 1-5 minutes and checks the repository for changes. If changes are detected, a build starts and will verify that the current state of the code is valid.

Once the build finishes, if it is successful, projecy-name-develop-to-development will start (triggered on project-name-develop success). It will deploy the stable code so that users may test it.

A whole change cycle will generally take from 1 to 30 minutes depending on how many tests you have and how well you've been able to optimize your jenkins build workflow.

Here's a list of things to try/check:

  • If you are running phpunit with code coverage, disable it and run it in a separate jenkins project. Code coverage is 2-5x slower than without it. When you are running the tests, you want to know the results fast and code coverage should not be a priority. Speed is the priority.
  • If you are running tests against a database and the tests requires setting up and tearing down the database (either just truncating the tables or full DROP tables), search for ways to avoid hitting the database or how to improve performance. For example, if you are testing using SQLite, run an initial database migration and seeding and copy the resulting .sqlite file so that it can be copied on test setup instead of migrating/seeding every time.
  • If migrating/seeding takes a long time, keep the resulting .sqlite file and only rebuild it if its source files (dependencies) have changed. On a project, you will run tests much more often than you will be rebuilding the .sqlite file, so it is worth investing in developing such a tool.
  • Since php is single threaded, look for tools that will enable you to do multi-process php testing. An example of such tool is liuggio/fastest. Depending on the number of processors/cores you have available, you could see a 4-8x gain in speed.
  • If you have the money/hardware, distribute testing over many machines. If you want a unified phpunit code coverage/results, you can use phpcov to merge separate test results into a single result file.

The following depicts how I “solved” a problem I recently had regarding munin, its mysql plugins and the shared memory cache library used by the plugins (written in perl and using IPC::ShareLite).

First off, let’s begin with a description of the problem. I posted the following on serverfault.com in hope I’d get help from someone more experienced than I am.

I’ve recently setup a munin-node on a CentOS server. All was working fine until I tried to add the apache plugin (which works fine).

For some odd reason, the mysql plugins for munin that used to work ceased to work… I’m now getting a weird error whenever I’m running the plugin with munin-run. For instance

munin-run mysql_files_tables

returns me


IPC::ShareLite store() error: Identifier removed at /usr/lib/perl5/vendor_perl/5.8.8/Cache/SharedMemoryBackend.pm line 156

but sometimes it will also return


table_open_cache.value 64
Open_files.value 58
Open_tables.value 64
Opened_tables.value 19341

but after a while it will revert to the previous error.

I do not have any knowledge about the IPC or the ShareLite library so I don’t really know were to start looking. Since it is a module related to shared memory, I tried tracking down shared memory segments with ipcs without much success.

I haven’t yet rebooted the machine as it is used for many projects (I’d obviously like to be able to diagnose the problem without requiring a restart if it was possible).

Has anyone faced this problem? (a quick search on google didn’t present any relevant help)

Thanks for the help!

Obviously, one can see quickly that this is a quite specific question that not many may have actually encountered. Thus, I didn’t expect to receive much help out of it (and I didn’t).

I had left this issue on the side for a couple of days hoping to come back to it at some point. Munin and the mysql plugins were installed on two servers and it was working fine on both of them (and a third one as master node). After a minor change, one of two client nodes stopped working correctly while the other was still fine. After a couple of days though the second server also decided to exhibit a similar issue…

Tonight I remembered about strace, which is pretty awesome in circumstances like this one. I went ahead and launched strace munin-run mysql_files_tables which outputted a lot of stuff and then stopped at the following point:


...
ioctl(4, SNDCTL_TMR_TIMEBASE or TCGETS, 0x7fff13da8e30) = -1 ENOTTY (Inappropriate ioctl for device)
lseek(4, 0, SEEK_CUR)                   = 0
read(4, "# Carp::Heavy uses some variable"..., 4096) = 4096
brk(0x163e7000)                         = 0x163e7000
read(4, "\n    redo if $Internal{$caller};"..., 4096) = 1737
read(4, "", 4096)                       = 0
close(4)                                = 0
write(2, "IPC::ShareLite store() error: Id"..., 123IPC::ShareLite store() error: Identifier removed at /usr/lib/perl5/vendor_perl/5.8.8/Cache/SharedMemoryBackend.pm line 156
) = 123
semop(14581770, 0x2ab08bb67cf0, 3

and when it is actually fixed, the application would end instead (outputting a bunch of stuff such as the following)


...
stat("/usr/lib64/perl5/auto/Storable/_freeze.al", {st_mode=S_IFREG|0644, st_size=706, ...}) = 0
stat("/usr/lib64/perl5/auto/Storable/_freeze.al", {st_mode=S_IFREG|0644, st_size=706, ...}) = 0
open("/usr/lib64/perl5/auto/Storable/_freeze.al", O_RDONLY) = 4
ioctl(4, SNDCTL_TMR_TIMEBASE or TCGETS, 0x7fffe7223570) = -1 ENOTTY (Inappropriate ioctl for device)
lseek(4, 0, SEEK_CUR)                   = 0
read(4, "# NOTE: Derived from ../../lib/S"..., 4096) = 706
read(4, "", 4096)                       = 0
close(4)                                = 0
semop(917514, {{1, 0, 0}, {2, 0, 0}, {2, 1, SEM_UNDO}}, 3) = 0
semop(917514, {{2, -1, SEM_UNDO|IPC_NOWAIT}}, 1) = 0
semop(917514, {{1, 0, 0}, {2, 0, 0}, {2, 1, SEM_UNDO}}, 3) = 0
shmdt(0x7fc30021f000)                   = 0
semop(917514, {{2, -1, SEM_UNDO|IPC_NOWAIT}}, 1) = 0
...

What you can see in the first output above is pretty interesting. The semop call gives you the semid the process is trying to obtain (the semaphore used to synchronize different processes using the same shared memory). The signature of the semop function is as follow:


int semop(int semid, struct sembuf *sops, unsigned nsops);

where
semid: semaphore id
sops: pointer to a sembuf struct


struct sembuf {
    u_short sem_num; /* semaphore # */
    short   sem_op;  /* semaphore operation */
    short   sem_flg; /* operation flags */
};

nsops: the length of sops

Upon first inspection, you can see that the sembuf in the first case seems to be invalid if you compare it with the working version where it is actually resolved (strace displays something such as {{2, -1, SEM_UNDO|IPC_NOWAIT}} instead of 0x2ab08bb67cf0. But that is not helping me much.

With that semid you can do two things: first, you can check if it is still alive by calling ipcs, second, you can remove it with ipcrm -s semid.

In my case the “fix” itself was to remove the semaphore that the plugin wasn’t able to obtain (the reason of this still elude me though). After the removal of the semaphore, it is possible again to run munin correctly and the identifier removed error is gone.

I will have to do more research as to how/why this issue occurs as I’ve seen it happen only on CentOS machines so far (the master server is a Debian machine).

For some odd reason it seems that the system designed into TortoiseGit doesn't allow the user to interact with git when it requires user interaction. For instance, accepting self-signed certificated is not possible, which gives you the known issue 318.

As of TortoiseGit 1.8.5.0, it is still not possible to accept certificates through the GUI. But it is possible to get your git repository to work with TortoiseGit (and work with the required certificate).

You will need to have msysgit installed and available in your PATH for the following to work.

The first step is to run some git command, for instance git clone https://myserver.com/depot via a command line so that git may auto-accept (or ask you to accept) the certificate. This step is crucial to get the certificate details saved onto your machine.

What you will want to do next is go to C:\Program Files (x86)\Git\.subversion and copy everything into %USERPROFILE%\.subversion. Basically, this should copy the certicates that were accepted by msysgit so they can be used by TortoiseGit.

Another, and possibly better solution, is to create a symbolic link so that those 2 folders are in fact a single one. For instance, you could do something such as


move %USERPROFILE%\.subversion %USERPROFILE%\.subversion_backup
mklink /D %USERPROFILE%\.subversion "C:\Program Files (x86)\Git\.subversion"

which will make %USERPROFILE%\.subversion point to your C:\Program Files (x86)\Git\.subversion folder. This has the benefit that any future certificate will work both for msysgit and TortoiseGit.

Thanks to Mexx’ C# Corner for pointing out the solution.

I was trying to get PHPUnit to give me some coverage report for a project I had not worked on for a long time. I had received a github pull request from someone and I wanted to see what the coverage was on the project to see if the submitter had done a good job of covering his code, but I couldn't get PHPUnit to generate a report that contained any data. All I would get was a couple of empty directory folders pages, which was useless.

I had code coverage work on another project in the same environment I was in, so I was pretty sure that my problem had to do either with how I had setup PHPUnit for that particular project, or that something else was interfering with the report generation.

I tried a couple of things, starting by calling phpunit from the command line using different arguments:


--coverage-html report test\symbol_test.php

Would generate some report with data in it, good!


-c test\phpunit.xml (logging set in phpunit.xml)

Would generate an empty report, not good...


--coverage-html report -c test\phpunit.xml

Would generate an empty report, not good...

So at that point I saw that it was working correctly and that something was definitely wrong with my phpunit.xml configuration file. I went back to the phpunit.de manual, specifically on the configuration page, and tried to figure out my problem.

For code coverage to be included in your report, you have to add a filter, be it a blacklist or a whitelist, but you have to have a filter.

So I quickly added a filter such as


<filter>
    <whitelist>
        <directory>../</directory>
    </whitelist>
</filter>

which would whitelist everything that is in the project (my project root is one level above test). Ran phpunit in the test folder and I finally got a report with data!